Merge "Create AppDataUsageAppSettingsController" into main
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
index 7ffa0c9..820a9d5 100644
--- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -50,6 +50,7 @@
 const val MVNO_TYPE = "mvnoType"
 const val MVNO_MATCH_DATA = "mvnoMatchData"
 const val EDIT_URL = "editUrl"
+const val INSERT_URL = "insertUrl"
 
 object ApnEditPageProvider : SettingsPageProvider {
 
@@ -66,7 +67,10 @@
 
     @Composable
     override fun Page(arguments: Bundle?) {
-        val apnDataInit = ApnData()
+        val uriString = arguments!!.getString(URI)
+        val uriInit = Uri.parse(String(Base64.getDecoder().decode(uriString)))
+        val subId = arguments.getInt(SUB_ID)
+        val apnDataInit = getApnDataInit(arguments, LocalContext.current, uriInit, subId)
         val apnDataCur = remember {
             mutableStateOf(apnDataInit)
         }
diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt
new file mode 100644
index 0000000..3ce7567
--- /dev/null
+++ b/src/com/android/settings/network/apn/ApnRepository.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2023 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.network.apn
+
+import android.content.Context
+import android.net.Uri
+import android.provider.Telephony
+import android.util.Log
+import com.android.settings.R
+import java.util.Locale
+
+const val NAME_INDEX = 1
+const val APN_INDEX = 2
+const val PROXY_INDEX = 3
+const val PORT_INDEX = 4
+const val USER_INDEX = 5
+const val SERVER_INDEX = 6
+const val PASSWORD_INDEX = 7
+const val MMSC_INDEX = 8
+const val MCC_INDEX = 9
+const val MNC_INDEX = 10
+const val MMSPROXY_INDEX = 12
+const val MMSPORT_INDEX = 13
+const val AUTH_TYPE_INDEX = 14
+const val TYPE_INDEX = 15
+const val PROTOCOL_INDEX = 16
+const val CARRIER_ENABLED_INDEX = 17
+const val NETWORK_TYPE_INDEX = 18
+const val ROAMING_PROTOCOL_INDEX = 19
+const val MVNO_TYPE_INDEX = 20
+const val MVNO_MATCH_DATA_INDEX = 21
+const val EDITED_INDEX = 22
+const val USER_EDITABLE_INDEX = 23
+const val CARRIER_ID_INDEX = 24
+
+val sProjection = arrayOf(
+    Telephony.Carriers._ID,  // 0
+    Telephony.Carriers.NAME,  // 1
+    Telephony.Carriers.APN,  // 2
+    Telephony.Carriers.PROXY,  // 3
+    Telephony.Carriers.PORT,  // 4
+    Telephony.Carriers.USER,  // 5
+    Telephony.Carriers.SERVER,  // 6
+    Telephony.Carriers.PASSWORD,  // 7
+    Telephony.Carriers.MMSC,  // 8
+    Telephony.Carriers.MCC,  // 9
+    Telephony.Carriers.MNC,  // 10
+    Telephony.Carriers.NUMERIC,  // 11
+    Telephony.Carriers.MMSPROXY,  // 12
+    Telephony.Carriers.MMSPORT,  // 13
+    Telephony.Carriers.AUTH_TYPE,  // 14
+    Telephony.Carriers.TYPE,  // 15
+    Telephony.Carriers.PROTOCOL,  // 16
+    Telephony.Carriers.CARRIER_ENABLED,  // 17
+    Telephony.Carriers.NETWORK_TYPE_BITMASK, // 18
+    Telephony.Carriers.ROAMING_PROTOCOL,  // 19
+    Telephony.Carriers.MVNO_TYPE,  // 20
+    Telephony.Carriers.MVNO_MATCH_DATA,  // 21
+    Telephony.Carriers.EDITED_STATUS,  // 22
+    Telephony.Carriers.USER_EDITABLE,  // 23
+    Telephony.Carriers.CARRIER_ID // 24
+)
+
+const val TAG = "ApnRepository"
+
+/**
+ * Query apn related information based on uri.
+ * @param uri URI data used for query.
+ *
+ * @return Stored apn related information.
+ */
+fun getApnDataFromUri(uri: Uri, context: Context): ApnData {
+    var apnData = ApnData()
+    val contentResolver = context.contentResolver
+    val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList()
+    val mvnoTypeOptions = context.resources.getStringArray(R.array.mvno_type_entries).toList()
+
+    contentResolver.query(
+        uri,
+        sProjection,
+        null /* selection */,
+        null /* selectionArgs */,
+        null /* sortOrder */
+    ).use { cursor ->
+        if (cursor != null && cursor.moveToFirst()) {
+            val name = cursor.getString(NAME_INDEX)
+            val apn = cursor.getString(APN_INDEX)
+            val proxy = cursor.getString(PROXY_INDEX)
+            val port = cursor.getString(PORT_INDEX)
+            val userName = cursor.getString(USER_INDEX)
+            val server = cursor.getString(SERVER_INDEX)
+            val passWord = cursor.getString(PASSWORD_INDEX)
+            val mmsc = cursor.getString(MMSC_INDEX)
+            val mcc = cursor.getString(MCC_INDEX)
+            val mnc = cursor.getString(MNC_INDEX)
+            val mmsProxy = cursor.getString(MMSPROXY_INDEX)
+            val mmsPort = cursor.getString(MMSPORT_INDEX)
+            val authType = cursor.getInt(AUTH_TYPE_INDEX)
+            val apnType = cursor.getString(TYPE_INDEX)
+            val apnProtocol = convertProtocol2Options(cursor.getString(PROTOCOL_INDEX), context)
+            val apnRoaming =
+                convertProtocol2Options(cursor.getString(ROAMING_PROTOCOL_INDEX), context)
+            val apnEnable = cursor.getInt(CARRIER_ENABLED_INDEX) == 1
+            val networkType = cursor.getLong(NETWORK_TYPE_INDEX)
+            val mvnoType = cursor.getString(MVNO_TYPE_INDEX)
+            val mvnoValue = cursor.getString(MVNO_MATCH_DATA_INDEX)
+
+            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,
+                apn = apn,
+                proxy = proxy,
+                port = port,
+                userName = userName,
+                passWord = passWord,
+                server = server,
+                mmsc = mmsc,
+                mmsProxy = mmsProxy,
+                mmsPort = mmsPort,
+                mcc = mcc,
+                mnc = mnc,
+                authType = authType,
+                apnType = apnType,
+                apnProtocol = apnProtocolOptions.indexOf(apnProtocol),
+                apnRoaming = apnProtocolOptions.indexOf(apnRoaming),
+                apnEnable = apnEnable,
+                networkType = networkType,
+                mvnoType = mvnoTypeOptions.indexOf(mvnoType),
+                mvnoValue = mvnoValue,
+                edited = edited,
+                userEditable = userEditable,
+                carrierId = carrierId
+            )
+        }
+    }
+    if (apnData.name == "") {
+        Log.d(TAG, "Can't get apnData from Uri $uri")
+    }
+    return apnData
+}
+
+/**
+ * Returns The UI choice (e.g., "IPv4/IPv6") corresponding to the given
+ * raw value of the protocol preference (e.g., "IPV4V6"). If unknown,
+ * return null.
+ *
+ * @return UI choice
+ */
+private fun convertProtocol2Options(raw: String, context: Context): String {
+    val apnProtocolOptions = context.resources.getStringArray(R.array.apn_protocol_entries).toList()
+    val apnProtocolValues = context.resources.getStringArray(R.array.apn_protocol_values).toList()
+
+    var uRaw = raw.uppercase(Locale.getDefault())
+    uRaw = if (uRaw == "IPV4") "IP" else uRaw
+    val protocolIndex = apnProtocolValues.indexOf(uRaw)
+    return if (protocolIndex == -1) {
+        ""
+    } else {
+        try {
+            apnProtocolOptions[protocolIndex]
+        } catch (e: ArrayIndexOutOfBoundsException) {
+            ""
+        }
+    }
+}
diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt
index c8d1b83..ac9f28b 100644
--- a/src/com/android/settings/network/apn/ApnStatus.kt
+++ b/src/com/android/settings/network/apn/ApnStatus.kt
@@ -16,8 +16,16 @@
 
 package com.android.settings.network.apn
 
+import android.content.Context
+import android.net.Uri
+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 com.android.internal.util.ArrayUtils
+import com.android.settings.R
 
 data class ApnData(
     val name: String = "",
@@ -42,26 +50,147 @@
     var mvnoValue: String = "",
     val edited: Int = Telephony.Carriers.USER_EDITED,
     val userEditable: Int = 1,
-    val carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID
-) {
-    var nameEnabled = true
-    var apnEnabled = true
-    var proxyEnabled = true
-    var portEnabled = true
-    var userNameEnabled = true
-    var passWordEnabled = true
-    var serverEnabled = true
-    var mmscEnabled = true
-    var mmsProxyEnabled = true
-    var mmsPortEnabled = true
-    var mccEnabled = true
-    var mncEnabled = true
-    var authTypeEnabled = true
-    var apnTypeEnabled = true
-    var apnProtocolEnabled = true
-    var apnRoamingEnabled = true
-    var apnEnableEnabled = true
-    var networkTypeEnabled = true
-    var mvnoTypeEnabled = true
-    var mvnoValueEnabled = false
+    val carrierId: Int = TelephonyManager.UNKNOWN_CARRIER_ID,
+    val nameEnabled: Boolean = true,
+    val apnEnabled: Boolean = true,
+    val proxyEnabled: Boolean = true,
+    val portEnabled: Boolean = true,
+    val userNameEnabled: Boolean = true,
+    val passWordEnabled: Boolean = true,
+    val serverEnabled: Boolean = true,
+    val mmscEnabled: Boolean = true,
+    val mmsProxyEnabled: Boolean = true,
+    val mmsPortEnabled: Boolean = true,
+    val mccEnabled: Boolean = true,
+    val mncEnabled: Boolean = true,
+    val authTypeEnabled: Boolean = true,
+    val apnTypeEnabled: Boolean = true,
+    val apnProtocolEnabled: Boolean = true,
+    val apnRoamingEnabled: Boolean = true,
+    val apnEnableEnabled: Boolean = true,
+    val networkTypeEnabled: Boolean = true,
+    val mvnoTypeEnabled: Boolean = true,
+    val mvnoValueEnabled: Boolean = false,
+    val newApn: Boolean = false,
+)
+
+data class CustomizedConfig(
+    val newApn: Boolean = false,
+    val readOnlyApn: Boolean = false,
+    val isAddApnAllowed: Boolean = true,
+    val readOnlyApnTypes: List<String> = emptyList(),
+    val readOnlyApnFields: List<String> = emptyList(),
+    val defaultApnTypes: List<String> = emptyList(),
+    val defaultApnProtocol: String = "",
+    val defaultApnRoamingProtocol: String = "",
+)
+
+/**
+ * Initialize ApnData according to the arguments.
+ * @param arguments The data passed in when the user calls PageProvider.
+ * @param uriInit The decoded user incoming uri data in Page.
+ * @param subId The subId obtained in arguments.
+ *
+ * @return Initialized CustomizedConfig information.
+ */
+fun getApnDataInit(arguments: Bundle, context: Context, uriInit: Uri, subId: Int): ApnData {
+
+    val uriType = arguments.getString(URI_TYPE)!!
+    val mvnoType = arguments.getString(MVNO_TYPE)
+    val mvnoValue = arguments.getString(MVNO_MATCH_DATA)
+    val mvnoTypeOptions = context.resources.getStringArray(R.array.mvno_type_entries).toList()
+
+    val configManager =
+        context.getSystemService(Context.CARRIER_CONFIG_SERVICE) as CarrierConfigManager
+    getCarrierCustomizedConfig(configManager, subId)
+
+    if (!uriInit.isPathPrefixMatch(Telephony.Carriers.CONTENT_URI)) {
+        Log.e(TAG, "Insert request not for carrier table. Uri: $uriInit")
+        return ApnData() //TODO: finish
+    }
+
+    var apnDataInit = when (uriType) {
+        EDIT_URL -> getApnDataFromUri(uriInit, context)
+        INSERT_URL -> ApnData(
+            mvnoType = mvnoTypeOptions.indexOf(mvnoType!!),
+            mvnoValue = mvnoValue!!
+        )
+
+        else -> ApnData() //TODO: finish
+    }
+
+    if (uriType == INSERT_URL) {
+        apnDataInit = apnDataInit.copy(newApn = true)
+    }
+
+    // TODO: mvnoDescription
+    apnDataInit = apnDataInit.copy(
+        apnEnableEnabled =
+        context.resources.getBoolean(R.bool.config_allow_edit_carrier_enabled)
+    )
+    // TODO: mIsCarrierIdApn & disableInit(apnDataInit)
+    return apnDataInit
 }
+
+/**
+ * Initialize CustomizedConfig information through subId.
+ * @param subId subId information obtained from arguments.
+ *
+ * @return Initialized CustomizedConfig information.
+ */
+fun getCarrierCustomizedConfig(configManager: CarrierConfigManager, subId: Int): CustomizedConfig {
+    val b = configManager.getConfigForSubId(
+        subId,
+        CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY,
+        CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY,
+        CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY,
+        CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING,
+        CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING,
+        CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL
+    )
+    val customizedConfig = CustomizedConfig(
+        readOnlyApnTypes = b.getStringArray(
+            CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY
+        )?.toList() ?: emptyList(), readOnlyApnFields = b.getStringArray(
+            CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY
+        )?.toList() ?: emptyList(), defaultApnTypes = b.getStringArray(
+            CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY
+        )?.toList() ?: emptyList(), defaultApnProtocol = b.getString(
+            CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING
+        ) ?: "", defaultApnRoamingProtocol = b.getString(
+            CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING
+        ) ?: "", isAddApnAllowed = b.getBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL)
+    )
+    if (!ArrayUtils.isEmpty(customizedConfig.readOnlyApnTypes)) {
+        Log.d(
+            TAG,
+            "getCarrierCustomizedConfig: read only APN type: " + customizedConfig.readOnlyApnTypes.joinToString(
+                ", "
+            )
+        )
+    }
+    if (!ArrayUtils.isEmpty(customizedConfig.defaultApnTypes)) {
+        Log.d(
+            TAG,
+            "getCarrierCustomizedConfig: default apn types: " + customizedConfig.defaultApnTypes.joinToString(
+                ", "
+            )
+        )
+    }
+    if (!TextUtils.isEmpty(customizedConfig.defaultApnProtocol)) {
+        Log.d(
+            TAG,
+            "getCarrierCustomizedConfig: default apn protocol: ${customizedConfig.defaultApnProtocol}"
+        )
+    }
+    if (!TextUtils.isEmpty(customizedConfig.defaultApnRoamingProtocol)) {
+        Log.d(
+            TAG,
+            "getCarrierCustomizedConfig: default apn roaming protocol: ${customizedConfig.defaultApnRoamingProtocol}"
+        )
+    }
+    if (!customizedConfig.isAddApnAllowed) {
+        Log.d(TAG, "getCarrierCustomizedConfig: not allow to add new APN")
+    }
+    return customizedConfig
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt
index 8902c73..8d1b223 100644
--- a/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnEditPageProviderTest.kt
@@ -256,9 +256,9 @@
             .performScrollToNode(hasText(passwordTitle, true))
         composeTestRule.onNodeWithText(passwordTitle, true).assertIsDisplayed()
     }
-    
+
     private companion object {
         const val NETWORK_TYPE_UNSPECIFIED = "Unspecified"
         const val NETWORK_TYPE_LTE = "LTE"
     }
-}
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..47f2a7f
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnRepositoryTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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.network.apn
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.MatrixCursor
+import android.net.Uri
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class ApnRepositoryTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val mContentResolver = mock<ContentResolver> {}
+    private val uri = mock<Uri> {}
+
+    @Test
+    fun getApnDataFromUri() {
+        // mock out resources and the feature provider
+        val cursor = MatrixCursor(sProjection)
+        cursor.addRow(
+            arrayOf<Any?>(
+                0, "name", "apn", "proxy", "port",
+                "userName", "server", "passWord", "mmsc", "mcc", "mnc", "numeric",
+                "mmsProxy", "mmsPort", 0, "apnType", "apnProtocol", 0,
+                0, "apnRoaming", "mvnoType", "mvnoValue", 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")
+    }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/network/apn/ApnStatusTest.kt b/tests/spa_unit/src/com/android/settings/network/apn/ApnStatusTest.kt
new file mode 100644
index 0000000..e590c28
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/apn/ApnStatusTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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.network.apn
+
+import android.os.PersistableBundle
+import android.telephony.CarrierConfigManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+class ApnStatusTest {
+    private val subId = 1
+    private val configManager = mock<CarrierConfigManager> {
+        val p = PersistableBundle()
+        p.putBoolean(CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL, true)
+        on {
+            getConfigForSubId(
+                subId,
+                CarrierConfigManager.KEY_READ_ONLY_APN_TYPES_STRING_ARRAY,
+                CarrierConfigManager.KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY,
+                CarrierConfigManager.KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY,
+                CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_PROTOCOL_STRING,
+                CarrierConfigManager.Apn.KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING,
+                CarrierConfigManager.KEY_ALLOW_ADDING_APNS_BOOL
+            )
+        } doReturn p
+    }
+
+    @Test
+    fun getCarrierCustomizedConfig_test() {
+        assert(getCarrierCustomizedConfig(configManager, subId).isAddApnAllowed)
+    }
+}
\ No newline at end of file