Merge "[Shadow] Update the string and the dismiss button" into main
diff --git a/res/layout/search_bar.xml b/res/layout/search_bar.xml
index dfc9596..1e83e22 100644
--- a/res/layout/search_bar.xml
+++ b/res/layout/search_bar.xml
@@ -34,6 +34,9 @@
             android:paddingStart="@dimen/search_bar_padding_start"
             android:paddingEnd="@dimen/search_bar_padding_end"
             android:background="@drawable/search_bar_selected_background"
+            android:focusable="true"
+            android:focusableInTouchMode="true"
+            android:nextFocusForward="@+id/homepage_container"
             android:contentInsetStartWithNavigation="@dimen/search_bar_content_inset"
             android:navigationIcon="@drawable/ic_homepage_search">
             <TextView
diff --git a/src/com/android/settings/localepicker/TermsOfAddressFeminineController.java b/src/com/android/settings/localepicker/TermsOfAddressFeminineController.java
index fdc4589..2227297 100644
--- a/src/com/android/settings/localepicker/TermsOfAddressFeminineController.java
+++ b/src/com/android/settings/localepicker/TermsOfAddressFeminineController.java
@@ -35,7 +35,7 @@
 
     @Override
     protected int getMetricsActionKey() {
-        return 0;
+        return SettingsEnums.ACTION_TERMS_OF_ADDRESS_SPECIFIED;
     }
 
     @Override
diff --git a/src/com/android/settings/localepicker/TermsOfAddressMasculineController.java b/src/com/android/settings/localepicker/TermsOfAddressMasculineController.java
index 8601113..1548be2 100644
--- a/src/com/android/settings/localepicker/TermsOfAddressMasculineController.java
+++ b/src/com/android/settings/localepicker/TermsOfAddressMasculineController.java
@@ -35,7 +35,7 @@
 
     @Override
     protected int getMetricsActionKey() {
-        return 0;
+        return SettingsEnums.ACTION_TERMS_OF_ADDRESS_SPECIFIED;
     }
 
     @Override
diff --git a/src/com/android/settings/localepicker/TermsOfAddressNeutralController.java b/src/com/android/settings/localepicker/TermsOfAddressNeutralController.java
index e67bd4d..0863c4f 100644
--- a/src/com/android/settings/localepicker/TermsOfAddressNeutralController.java
+++ b/src/com/android/settings/localepicker/TermsOfAddressNeutralController.java
@@ -35,7 +35,7 @@
 
     @Override
     protected int getMetricsActionKey() {
-        return 0;
+        return SettingsEnums.ACTION_TERMS_OF_ADDRESS_SPECIFIED;
     }
 
     @Override
diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt
index ae655da..2266983 100644
--- a/src/com/android/settings/network/apn/ApnRepository.kt
+++ b/src/com/android/settings/network/apn/ApnRepository.kt
@@ -20,6 +20,8 @@
 import android.content.Context
 import android.net.Uri
 import android.provider.Telephony
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
 import android.util.Log
 import com.android.settings.R
 import com.android.settingslib.utils.ThreadUtils
@@ -43,7 +45,6 @@
 const val ROAMING_PROTOCOL_INDEX = 16
 const val EDITED_INDEX = 17
 const val USER_EDITABLE_INDEX = 18
-const val CARRIER_ID_INDEX = 19
 
 val sProjection = arrayOf(
     Telephony.Carriers._ID,  // 0
@@ -65,7 +66,6 @@
     Telephony.Carriers.ROAMING_PROTOCOL,  // 16
     Telephony.Carriers.EDITED_STATUS,  // 17
     Telephony.Carriers.USER_EDITABLE,  // 18
-    Telephony.Carriers.CARRIER_ID // 19
 )
 
 const val TAG = "ApnRepository"
@@ -109,7 +109,6 @@
 
             val edited = cursor.getInt(EDITED_INDEX)
             val userEditable = cursor.getInt(USER_EDITABLE_INDEX)
-            val carrierId = cursor.getInt(CARRIER_ID_INDEX)
 
             apnData = apnData.copy(
                 name = name,
@@ -130,7 +129,6 @@
                 networkType = networkType,
                 edited = edited,
                 userEditable = userEditable,
-                carrierId = carrierId
             )
         }
     }
@@ -199,29 +197,44 @@
     }
 }
 
+/** Not allowing add duplicated items, if the values of the following keys are all identical. */
+private val NonDuplicatedKeys = setOf(
+    Telephony.Carriers.APN,
+    Telephony.Carriers.PROXY,
+    Telephony.Carriers.PORT,
+    Telephony.Carriers.MMSC,
+    Telephony.Carriers.MMSPROXY,
+    Telephony.Carriers.MMSPORT,
+    Telephony.Carriers.PROTOCOL,
+    Telephony.Carriers.ROAMING_PROTOCOL,
+)
+
 fun isItemExist(apnData: ApnData, context: Context): String? {
-    var contentValueMap = apnData.getContentValueMap(context)
-    val removedList = arrayListOf(
-        Telephony.Carriers.NAME, Telephony.Carriers.USER,
-        Telephony.Carriers.SERVER, Telephony.Carriers.PASSWORD, Telephony.Carriers.AUTH_TYPE,
-        Telephony.Carriers.TYPE, Telephony.Carriers.NETWORK_TYPE_BITMASK,
-        Telephony.Carriers.CARRIER_ENABLED
-    )
-    contentValueMap =
-        contentValueMap.filterNot { removedList.contains(it.key) } as MutableMap<String, Any>
+    val contentValueMap = apnData.getContentValueMap(context).filterKeys { it in NonDuplicatedKeys }
     val list = contentValueMap.entries.toList()
     val selection = list.joinToString(" AND ") { "${it.key} = ?" }
     val selectionArgs: Array<String> = list.map { it.value.toString() }.toTypedArray()
     context.contentResolver.query(
-        Telephony.Carriers.CONTENT_URI,
-        sProjection,
-        selection /* selection */,
-        selectionArgs /* selectionArgs */,
-        null /* sortOrder */
+        Uri.withAppendedPath(Telephony.Carriers.SIM_APN_URI, apnData.subId.toString()),
+        /* projection = */ emptyArray(),
+        selection,
+        selectionArgs,
+        /* sortOrder = */ null,
     )?.use { cursor ->
         if (cursor.count > 0) {
             return context.resources.getString(R.string.error_duplicate_apn_entry)
         }
     }
     return null
-}
\ No newline at end of file
+}
+
+fun Context.getApnIdMap(subId: Int): Map<String, Any> {
+    val subInfo = getSystemService(SubscriptionManager::class.java)!!
+        .getActiveSubscriptionInfo(subId)
+    val carrierId = subInfo.carrierId
+    return if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
+        mapOf(Telephony.Carriers.CARRIER_ID to carrierId)
+    } else {
+        mapOf(Telephony.Carriers.NUMERIC to subInfo.mccString + subInfo.mncString)
+    }.also { Log.d(TAG, "[$subId] New APN item with id: $it") }
+}
diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt
index 141ec08..02e2814 100644
--- a/src/com/android/settings/network/apn/ApnStatus.kt
+++ b/src/com/android/settings/network/apn/ApnStatus.kt
@@ -22,13 +22,10 @@
 import android.os.Bundle
 import android.provider.Telephony
 import android.telephony.CarrierConfigManager
-import android.telephony.TelephonyManager
 import android.text.TextUtils
 import android.util.Log
-import androidx.compose.runtime.snapshots.SnapshotStateList
 import com.android.internal.util.ArrayUtils
 import com.android.settings.R
-import com.android.settings.network.apn.ApnNetworkTypes.getNetworkType
 import com.android.settings.network.apn.ApnTypes.APN_TYPES
 import com.android.settings.network.apn.ApnTypes.APN_TYPE_ALL
 import com.android.settings.network.apn.ApnTypes.APN_TYPE_EMERGENCY
@@ -56,7 +53,6 @@
     val networkType: Long = 0,
     val edited: Int = Telephony.Carriers.USER_EDITED,
     val userEditable: Int = 1,
-    val carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID,
     val nameEnabled: Boolean = true,
     val apnEnabled: Boolean = true,
     val proxyEnabled: Boolean = true,
@@ -78,34 +74,29 @@
     val validEnabled: Boolean = false,
     val customizedConfig: CustomizedConfig = CustomizedConfig()
 ) {
-    fun getContentValueMap(context: Context): MutableMap<String, Any> {
-        val simCarrierId =
-            context.getSystemService(TelephonyManager::class.java)!!
-                .createForSubscriptionId(subId)
-                .getSimCarrierId()
-        return mutableMapOf(
-            Telephony.Carriers.NAME to name, Telephony.Carriers.APN to apn,
-            Telephony.Carriers.PROXY to proxy, Telephony.Carriers.PORT to port,
-            Telephony.Carriers.MMSPROXY to mmsProxy, Telephony.Carriers.MMSPORT to mmsPort,
-            Telephony.Carriers.USER to userName, Telephony.Carriers.SERVER to server,
-            Telephony.Carriers.PASSWORD to passWord, Telephony.Carriers.MMSC to mmsc,
-            Telephony.Carriers.AUTH_TYPE to authType,
-            Telephony.Carriers.PROTOCOL to convertOptions2Protocol(apnProtocol, context),
-            Telephony.Carriers.ROAMING_PROTOCOL to convertOptions2Protocol(apnRoaming, context),
-            Telephony.Carriers.TYPE to apnType,
-            Telephony.Carriers.NETWORK_TYPE_BITMASK to networkType,
-            Telephony.Carriers.CARRIER_ENABLED to apnEnable,
-            Telephony.Carriers.EDITED_STATUS to Telephony.Carriers.USER_EDITED,
-            Telephony.Carriers.CARRIER_ID to simCarrierId
-        )
-    }
+    fun getContentValueMap(context: Context): Map<String, Any> = mapOf(
+        Telephony.Carriers.NAME to name,
+        Telephony.Carriers.APN to apn,
+        Telephony.Carriers.PROXY to proxy,
+        Telephony.Carriers.PORT to port,
+        Telephony.Carriers.USER to userName,
+        Telephony.Carriers.SERVER to server,
+        Telephony.Carriers.PASSWORD to passWord,
+        Telephony.Carriers.MMSC to mmsc,
+        Telephony.Carriers.MMSPROXY to mmsProxy,
+        Telephony.Carriers.MMSPORT to mmsPort,
+        Telephony.Carriers.AUTH_TYPE to authType,
+        Telephony.Carriers.PROTOCOL to convertOptions2Protocol(apnProtocol, context),
+        Telephony.Carriers.ROAMING_PROTOCOL to convertOptions2Protocol(apnRoaming, context),
+        Telephony.Carriers.TYPE to apnType,
+        Telephony.Carriers.NETWORK_TYPE_BITMASK to networkType,
+        Telephony.Carriers.CARRIER_ENABLED to apnEnable,
+        Telephony.Carriers.EDITED_STATUS to Telephony.Carriers.USER_EDITED,
+    )
 
-    fun getContentValues(context: Context): ContentValues {
-        val values = ContentValues()
-        val contentValueMap = getContentValueMap(context)
-        if (!newApn) contentValueMap.remove(Telephony.Carriers.CARRIER_ID)
-        contentValueMap.forEach { (key, value) -> values.putObject(key, value) }
-        return values
+    fun getContentValues(context: Context) = ContentValues().apply {
+        if (newApn) context.getApnIdMap(subId).forEach(::putObject)
+        getContentValueMap(context).forEach(::putObject)
     }
 }
 
diff --git a/src/com/android/settings/password/BiometricFragment.java b/src/com/android/settings/password/BiometricFragment.java
index 379ce80..90a1feb 100644
--- a/src/com/android/settings/password/BiometricFragment.java
+++ b/src/com/android/settings/password/BiometricFragment.java
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
 import android.hardware.biometrics.BiometricPrompt.AuthenticationResult;
 import android.hardware.biometrics.PromptInfo;
+import android.multiuser.Flags;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 
@@ -140,8 +141,14 @@
                 .setDisallowBiometricsIfPolicyExists(
                         promptInfo.isDisallowBiometricsIfPolicyExists())
                 .setShowEmergencyCallButton(promptInfo.isShowEmergencyCallButton())
-                .setReceiveSystemEvents(true)
-                .setAllowBackgroundAuthentication(true);
+                .setReceiveSystemEvents(true);
+
+        if (Flags.enableBiometricsToUnlockPrivateSpace()) {
+            promptBuilder = promptBuilder.setAllowBackgroundAuthentication(true /* allow */,
+                    promptInfo.shouldUseParentProfileForDeviceCredential());
+        } else {
+            promptBuilder = promptBuilder.setAllowBackgroundAuthentication(true /* allow */);
+        }
 
         // Check if the default subtitle should be used if subtitle is null/empty
         if (promptInfo.isUseDefaultSubtitle()) {
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index 4a760ad..f2ebd1f 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -311,6 +311,7 @@
             mForceVerifyPath = userProperties.isCredentialShareableWithParent();
             if (android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
                     && isBiometricAllowed(effectiveUserId, mUserId)) {
+                promptInfo.setUseParentProfileForDeviceCredential(true);
                 showBiometricPrompt(promptInfo, effectiveUserId);
                 launchedBiometric = true;
             } else {
diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt
index 44ec926..ec3b754 100644
--- a/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt
@@ -20,19 +20,39 @@
 import android.content.Context
 import android.database.MatrixCursor
 import android.net.Uri
+import android.provider.Telephony
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito
+import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
 import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class ApnRepositoryTest {
 
-    private val context: Context = ApplicationProvider.getApplicationContext()
-    private val mContentResolver = mock<ContentResolver> {}
+    private val contentResolver = mock<ContentResolver>()
+
+    private val mockSubscriptionInfo = mock<SubscriptionInfo> {
+        on { mccString } doReturn MCC
+        on { mncString } doReturn MNC
+    }
+
+    private val mockSubscriptionManager = mock<SubscriptionManager> {
+        on { getActiveSubscriptionInfo(SUB_ID) } doReturn mockSubscriptionInfo
+    }
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { contentResolver } doReturn contentResolver
+        on { getSystemService(SubscriptionManager::class.java) } doReturn mockSubscriptionManager
+    }
     private val uri = mock<Uri> {}
 
     @Test
@@ -40,7 +60,7 @@
         // mock out resources and the feature provider
         val cursor = MatrixCursor(sProjection)
         cursor.addRow(
-            arrayOf<Any?>(
+            arrayOf<Any>(
                 0,
                 "name",
                 "apn",
@@ -60,12 +80,41 @@
                 "apnRoaming",
                 0,
                 1,
-                0
             )
         )
-        val context = Mockito.spy(context)
-        whenever(context.contentResolver).thenReturn(mContentResolver)
-        whenever(mContentResolver.query(uri, sProjection, null, null, null)).thenReturn(cursor)
-        assert(getApnDataFromUri(uri, context).name == "name")
+        whenever(contentResolver.query(uri, sProjection, null, null, null)).thenReturn(cursor)
+
+        val apnData = getApnDataFromUri(uri, context)
+
+        assertThat(apnData.name).isEqualTo("name")
     }
-}
\ No newline at end of file
+
+    @Test
+    fun getApnIdMap_knownCarrierId() {
+        mockSubscriptionInfo.stub {
+            on { carrierId } doReturn CARRIER_ID
+        }
+
+        val idMap = context.getApnIdMap(SUB_ID)
+
+        assertThat(idMap).containsExactly(Telephony.Carriers.CARRIER_ID, CARRIER_ID)
+    }
+
+    @Test
+    fun getApnIdMap_unknownCarrierId() {
+        mockSubscriptionInfo.stub {
+            on { carrierId } doReturn TelephonyManager.UNKNOWN_CARRIER_ID
+        }
+
+        val idMap = context.getApnIdMap(SUB_ID)
+
+        assertThat(idMap).containsExactly(Telephony.Carriers.NUMERIC, MCC + MNC)
+    }
+
+    private companion object {
+        const val SUB_ID = 2
+        const val CARRIER_ID = 10
+        const val MCC = "310"
+        const val MNC = "101"
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
index c742bd7..69acee8 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
@@ -24,6 +24,7 @@
 import android.content.pm.PackageInfo
 import android.content.pm.PackageInstaller
 import android.content.pm.PackageManager
+import android.platform.test.flag.junit.SetFlagsRule
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotDisplayed
@@ -34,6 +35,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.settings.R
+import com.android.settings.flags.Flags as SettingsFlags
 import com.android.settingslib.applications.AppUtils
 import com.android.settingslib.spa.testutils.delay
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -54,6 +56,8 @@
     @get:Rule
     val composeTestRule = createComposeRule()
 
+    @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
     private lateinit var mockSession: MockitoSession
 
     @Spy
@@ -113,6 +117,7 @@
     fun launchButton_displayed_archivingDisabled() {
         whenever(packageManager.getLaunchIntentForPackage(PACKAGE_NAME)).thenReturn(Intent())
         featureFlags.setFlag(Flags.FLAG_ARCHIVING, false)
+        setFlagsRule.disableFlags(SettingsFlags.FLAG_APP_ARCHIVING)
         setContent()
 
         composeTestRule.onNodeWithText(context.getString(R.string.launch_instant_app))
diff --git a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressFeminineControllerTest.java b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressFeminineControllerTest.java
index d1e3078..c04e5f9 100644
--- a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressFeminineControllerTest.java
+++ b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressFeminineControllerTest.java
@@ -97,6 +97,7 @@
 
         selectedPreference.performClick();
 
+        assertThat(selectedPreference.getKey()).isEqualTo(KEY_FEMININE);
         assertThat(selectedPreference.isSelected()).isTrue();
         assertThat(pref.isSelected()).isFalse();
     }
diff --git a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressMasculineControllerTest.java b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressMasculineControllerTest.java
index 5bf3073..c2298be 100644
--- a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressMasculineControllerTest.java
+++ b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressMasculineControllerTest.java
@@ -97,6 +97,7 @@
 
         selectedPreference.performClick();
 
+        assertThat(selectedPreference.getKey()).isEqualTo(KEY_MASCULINE);
         assertThat(selectedPreference.isSelected()).isTrue();
         assertThat(pref.isSelected()).isFalse();
     }
diff --git a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNeutralControllerTest.java b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNeutralControllerTest.java
index e83f3cd..fb207fc 100644
--- a/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNeutralControllerTest.java
+++ b/tests/unit/src/com/android/settings/localepicker/TermsOfAddressNeutralControllerTest.java
@@ -97,6 +97,7 @@
 
         selectedPreference.performClick();
 
+        assertThat(selectedPreference.getKey()).isEqualTo(KEY_NEUTRAL);
         assertThat(selectedPreference.isSelected()).isTrue();
         assertThat(pref.isSelected()).isFalse();
     }