Merge "Fix IMEI is not selectable" into main
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
index 81da0bf..0ed54e7 100644
--- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -38,7 +38,6 @@
 import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeDisplayNames
 import com.android.settings.network.apn.ApnNetworkTypes.getNetworkTypeSelectedOptionsState
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuBox
 import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox
 import com.android.settingslib.spa.widget.editor.SettingsOutlinedTextField
@@ -186,10 +185,8 @@
             SwitchPreference(
                 object : SwitchPreferenceModel {
                     override val title = context.resources.getString(R.string.carrier_enabled)
-                    override val changeable =
-                        stateOf(apnData.apnEnableEnabled)
-                    override val checked =
-                        stateOf(apnData.apnEnable)
+                    override val changeable = { apnData.apnEnableEnabled }
+                    override val checked = { apnData.apnEnable }
                     override val onCheckedChange = { newChecked: Boolean ->
                         apnData = apnData.copy(apnEnable = newChecked)
                     }
diff --git a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
index 77d68b5..78ca15b 100644
--- a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
@@ -28,7 +28,7 @@
 import android.provider.DeviceConfig
 import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -56,18 +56,14 @@
     val presenter = remember { HibernationSwitchPresenter(context, app) }
     if (!presenter.isAvailable()) return
 
-    val isEligibleState = presenter.isEligibleFlow.collectAsStateWithLifecycle(initialValue = false)
+    val isEligibleState by presenter.isEligibleFlow.collectAsStateWithLifecycle(initialValue = false)
     val isCheckedState = presenter.isCheckedFlow.collectAsStateWithLifecycle(initialValue = null)
     SwitchPreference(remember {
         object : SwitchPreferenceModel {
             override val title = context.getString(R.string.unused_apps_switch)
             override val summary = { context.getString(R.string.unused_apps_switch_summary) }
-            override val changeable = isEligibleState
-
-            override val checked = derivedStateOf {
-                if (!changeable.value) false else isCheckedState.value
-            }
-
+            override val changeable = { isEligibleState }
+            override val checked = { if (changeable()) isCheckedState.value else false }
             override val onCheckedChange = presenter::onCheckedChange
         }
     })
diff --git a/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt
index c31eb7a..c990927 100644
--- a/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt
+++ b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt
@@ -24,10 +24,9 @@
 import android.content.pm.ApplicationInfo
 import android.os.PowerExemptionManager
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
 import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
 import com.android.settingslib.R
-import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.livedata.observeAsCallback
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
@@ -79,9 +78,10 @@
     }
 
     @Composable
-    override fun isAllowed(record: AlarmsAndRemindersAppRecord) =
-        if (record.isTrumped) stateOf(true)
-        else record.controller.isAllowed.observeAsState()
+    override fun isAllowed(record: AlarmsAndRemindersAppRecord): () -> Boolean? = when {
+        record.isTrumped -> ({ true })
+        else -> record.controller.isAllowed.observeAsCallback()
+    }
 
     override fun isChangeable(record: AlarmsAndRemindersAppRecord) = record.isChangeable
 
diff --git a/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
index c98b2ee..7f63e38 100644
--- a/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
+++ b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
@@ -24,8 +24,8 @@
 import android.content.pm.ApplicationInfo
 import android.os.UserManager
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
 import com.android.settings.R
+import com.android.settingslib.spa.livedata.observeAsCallback
 import com.android.settingslib.spaprivileged.model.app.AppOpsController
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
@@ -79,7 +79,7 @@
 
     @Composable
     override fun isAllowed(record: InstallUnknownAppsRecord) =
-        record.appOpsController.isAllowed.observeAsState()
+        record.appOpsController.isAllowed.observeAsCallback()
 
     override fun isChangeable(record: InstallUnknownAppsRecord) =
         isChangeable(record, getPotentialPackageNames(record.app.userId))
diff --git a/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt
index 3dede42..f02a6a1 100644
--- a/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt
+++ b/src/com/android/settings/spa/app/specialaccess/NfcTagAppsSettings.kt
@@ -23,8 +23,8 @@
 import android.nfc.NfcAdapter
 import android.util.Log
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
 import com.android.settings.R
+import com.android.settingslib.spa.livedata.observeAsCallback
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
 import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
@@ -100,7 +100,7 @@
 
     @Composable
     override fun isAllowed(record: NfcTagAppsSettingsRecord) =
-        record.controller.isAllowed.observeAsState()
+        record.controller.isAllowed.observeAsCallback()
 
     override fun isChangeable(record: NfcTagAppsSettingsRecord) = true
 
diff --git a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
index 5ed3615..cd615919 100644
--- a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
+++ b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
@@ -25,8 +25,8 @@
 import android.content.pm.PackageManager.PackageInfoFlags
 import android.util.Log
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
 import com.android.settings.R
+import com.android.settingslib.spa.livedata.observeAsCallback
 import com.android.settingslib.spaprivileged.model.app.AppOpsController
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.installed
@@ -90,7 +90,7 @@
 
     @Composable
     override fun isAllowed(record: PictureInPictureRecord) =
-        record.appOpsController.isAllowed.observeAsState()
+        record.appOpsController.isAllowed.observeAsCallback()
 
     override fun isChangeable(record: PictureInPictureRecord) = record.isSupport
 
diff --git a/src/com/android/settings/spa/network/AirplaneModePreference.kt b/src/com/android/settings/spa/network/AirplaneModePreference.kt
index 462c121..27261b6 100644
--- a/src/com/android/settings/spa/network/AirplaneModePreference.kt
+++ b/src/com/android/settings/spa/network/AirplaneModePreference.kt
@@ -20,8 +20,9 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.AirplanemodeActive
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
@@ -37,11 +38,12 @@
     val context = LocalContext.current
     val controller = remember { AirplaneModeController(context) }
     if (!controller.isAvailable()) return
+    val checked by controller.airplaneModeState.observeAsState(
+        initial = controller.isAirplaneModeOn()
+    )
     SwitchPreference(object : SwitchPreferenceModel {
         override val title = context.getString(R.string.airplane_mode)
-        override val checked = controller.airplaneModeState.observeAsState(
-            initial = controller.isAirplaneModeOn()
-        )
+        override val checked = { checked }
         override val onCheckedChange = { newChecked: Boolean ->
             controller.setChecked(newChecked)
         }
diff --git a/src/com/android/settings/spa/notification/AppNotificationsListModel.kt b/src/com/android/settings/spa/notification/AppNotificationsListModel.kt
index 692ffcb..2f3de3a 100644
--- a/src/com/android/settings/spa/notification/AppNotificationsListModel.kt
+++ b/src/com/android/settings/spa/notification/AppNotificationsListModel.kt
@@ -21,7 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.icu.text.RelativeDateTimeFormatter
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.produceState
 import com.android.settings.R
 import com.android.settings.applications.AppInfoBase
@@ -29,6 +29,7 @@
 import com.android.settings.spa.notification.SpinnerItem.Companion.toSpinnerItem
 import com.android.settingslib.spa.framework.util.asyncFilter
 import com.android.settingslib.spa.framework.util.asyncForEach
+import com.android.settingslib.spa.livedata.observeAsCallback
 import com.android.settingslib.spa.widget.ui.SpinnerOption
 import com.android.settingslib.spaprivileged.model.app.AppEntry
 import com.android.settingslib.spaprivileged.model.app.AppListModel
@@ -36,9 +37,11 @@
 import com.android.settingslib.spaprivileged.template.app.AppListItemModel
 import com.android.settingslib.spaprivileged.template.app.AppListTwoTargetSwitchItem
 import com.android.settingslib.utils.StringUtil
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
 
 data class AppNotificationsRecord(
     override val app: ApplicationInfo,
@@ -117,12 +120,15 @@
 
     @Composable
     override fun AppListItemModel<AppNotificationsRecord>.AppItem() {
+        val changeable by produceState(initialValue = false) {
+            withContext(Dispatchers.Default) {
+                value = repository.isChangeable(record.app)
+            }
+        }
         AppListTwoTargetSwitchItem(
             onClick = { navigateToAppNotificationSettings(app = record.app) },
-            checked = record.controller.isEnabled.observeAsState(),
-            changeable = produceState(initialValue = false) {
-                value = repository.isChangeable(record.app)
-            },
+            checked = record.controller.isEnabled.observeAsCallback(),
+            changeable = { changeable },
             onCheckedChange = record.controller::setEnabled,
         )
     }
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/WifiControlAppListModelTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/WifiControlAppListModelTest.kt
index c5c48f5..74aa861 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/WifiControlAppListModelTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/WifiControlAppListModelTest.kt
@@ -18,11 +18,8 @@
 
 import android.Manifest
 import android.app.AppOpsManager
-import android.app.AppOpsManager.MODE_ALLOWED
-import android.app.AppOpsManager.MODE_DEFAULT
 import android.content.Context
 import android.content.pm.ApplicationInfo
-import androidx.compose.runtime.State
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.lifecycle.MutableLiveData
 import androidx.test.core.app.ApplicationProvider
@@ -40,9 +37,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
 
 @ExperimentalCoroutinesApi
 @RunWith(AndroidJUnit4::class)
@@ -248,9 +245,9 @@
     }
 
     private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
-        lateinit var isAllowedState: State<Boolean?>
+        lateinit var isAllowedState: () -> Boolean?
         composeTestRule.setContent { isAllowedState = listModel.isAllowed(record) }
-        return isAllowedState.value
+        return isAllowedState()
     }
 
     private companion object {
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt
index f5d422d..4c65d90 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/AllFilesAccessTest.kt
@@ -33,33 +33,12 @@
     private val listModel = AllFilesAccessListModel(context)
 
     @Test
-    fun pageTitleResId() {
+    fun modelResourceIdAndProperties() {
         assertThat(listModel.pageTitleResId).isEqualTo(R.string.manage_external_storage_title)
-    }
-
-    @Test
-    fun switchTitleResId() {
         assertThat(listModel.switchTitleResId).isEqualTo(R.string.permit_manage_external_storage)
-    }
-
-    @Test
-    fun footerResId() {
-        assertThat(listModel.footerResId)
-            .isEqualTo(R.string.allow_manage_external_storage_description)
-    }
-
-    @Test
-    fun appOp() {
+        assertThat(listModel.footerResId).isEqualTo(R.string.allow_manage_external_storage_description)
         assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE)
-    }
-
-    @Test
-    fun permission() {
         assertThat(listModel.permission).isEqualTo(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
-    }
-
-    @Test
-    fun setModeByUid() {
         assertThat(listModel.setModeByUid).isTrue()
     }
 }
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt
index b56d997..b901043 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaManagementAppsTest.kt
@@ -33,33 +33,12 @@
     private val listModel = MediaManagementAppsListModel(context)
 
     @Test
-    fun pageTitleResId() {
+    fun modelResourceIdAndProperties() {
         assertThat(listModel.pageTitleResId).isEqualTo(R.string.media_management_apps_title)
-    }
-
-    @Test
-    fun switchTitleResId() {
-        assertThat(listModel.switchTitleResId)
-            .isEqualTo(R.string.media_management_apps_toggle_label)
-    }
-
-    @Test
-    fun footerResId() {
+        assertThat(listModel.switchTitleResId).isEqualTo(R.string.media_management_apps_toggle_label)
         assertThat(listModel.footerResId).isEqualTo(R.string.media_management_apps_description)
-    }
-
-    @Test
-    fun appOp() {
         assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_MANAGE_MEDIA)
-    }
-
-    @Test
-    fun permission() {
         assertThat(listModel.permission).isEqualTo(Manifest.permission.MANAGE_MEDIA)
-    }
-
-    @Test
-    fun setModeByUid() {
         assertThat(listModel.setModeByUid).isTrue()
     }
 }
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt
index 6054bb5..4229247 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt
@@ -67,18 +67,9 @@
     }
 
     @Test
-    fun pageTitleResId() {
+    fun modelResourceId() {
         assertThat(listModel.pageTitleResId).isEqualTo(R.string.picture_in_picture_title)
-    }
-
-    @Test
-    fun switchTitleResId() {
-        assertThat(listModel.switchTitleResId)
-            .isEqualTo(R.string.picture_in_picture_app_detail_switch)
-    }
-
-    @Test
-    fun footerResId() {
+        assertThat(listModel.switchTitleResId).isEqualTo(R.string.picture_in_picture_app_detail_switch)
         assertThat(listModel.footerResId).isEqualTo(R.string.picture_in_picture_app_detail_summary)
     }
 
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt
index 54ae6c6..9c6079d 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt
@@ -31,32 +31,12 @@
     private val listModel = TurnScreenOnAppsListModel(context)
 
     @Test
-    fun pageTitleResId() {
+    fun modelResourceIdAndProperties() {
         assertThat(listModel.pageTitleResId).isEqualTo(com.android.settingslib.R.string.turn_screen_on_title)
-    }
-
-    @Test
-    fun switchTitleResId() {
         assertThat(listModel.switchTitleResId).isEqualTo(com.android.settingslib.R.string.allow_turn_screen_on)
-    }
-
-    @Test
-    fun footerResId() {
         assertThat(listModel.footerResId).isEqualTo(com.android.settingslib.R.string.allow_turn_screen_on_description)
-    }
-
-    @Test
-    fun appOp() {
         assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_TURN_SCREEN_ON)
-    }
-
-    @Test
-    fun permission() {
         assertThat(listModel.permission).isEqualTo(Manifest.permission.TURN_SCREEN_ON)
-    }
-
-    @Test
-    fun setModeByUid() {
         assertThat(listModel.setModeByUid).isTrue()
     }
 }
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt
index 7d636b3..a2aa293 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt
@@ -17,34 +17,14 @@
     private val listModel = VoiceActivationAppsListModel(context)
 
     @Test
-    fun pageTitleResId() {
+    fun modelResourceIdAndProperties() {
         assertThat(listModel.pageTitleResId).isEqualTo(R.string.voice_activation_apps_title)
-    }
-
-    @Test
-    fun switchTitleResId() {
         assertThat(listModel.switchTitleResId).isEqualTo(R.string.permit_voice_activation_apps)
-    }
-
-    @Test
-    fun footerResId() {
-        assertThat(listModel.footerResId)
-            .isEqualTo(R.string.allow_voice_activation_apps_description)
-    }
-
-    @Test
-    fun appOp() {
+        assertThat(listModel.footerResId).isEqualTo(R.string.allow_voice_activation_apps_description)
         assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO)
-    }
-
-    @Test
-    fun permission() {
         assertThat(listModel.permission).isEqualTo(
-            Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
-    }
-
-    @Test
-    fun setModeByUid() {
+            Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO
+        )
         assertThat(listModel.setModeByUid).isTrue()
     }
 }
\ No newline at end of file