Add isEnabled API in SPP.

API isEnabled is used to specify whether the page is enabled during
migration. Once it is disabled, all its functionalities, such as
browsing / search / slice provider are disabled.

Bug: 244122804
Test: unit-test & local build gallery
Change-Id: I4f3f83e314b624fd2f6f83e0d1f64f426efb0350
This commit is contained in:
Zekan Qian
2023-01-16 13:55:20 +08:00
parent db87c40756
commit 839e10f801
9 changed files with 137 additions and 50 deletions

View File

@@ -49,7 +49,7 @@ import com.android.settingslib.spa.framework.compose.composable
import com.android.settingslib.spa.framework.compose.localNavController
import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.util.PageEvent
import com.android.settingslib.spa.framework.util.PageWithEvent
import com.android.settingslib.spa.framework.util.getDestination
import com.android.settingslib.spa.framework.util.getEntryId
import com.android.settingslib.spa.framework.util.getSessionName
@@ -118,32 +118,25 @@ private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<Settings
arguments = spp.parameter,
enterTransition = {
slideIntoContainer(
AnimatedContentScope.SlideDirection.Left,
animationSpec = slideEffect
AnimatedContentScope.SlideDirection.Left, animationSpec = slideEffect
) + fadeIn(animationSpec = fadeEffect)
},
exitTransition = {
slideOutOfContainer(
AnimatedContentScope.SlideDirection.Left,
animationSpec = slideEffect
AnimatedContentScope.SlideDirection.Left, animationSpec = slideEffect
) + fadeOut(animationSpec = fadeEffect)
},
popEnterTransition = {
slideIntoContainer(
AnimatedContentScope.SlideDirection.Right,
animationSpec = slideEffect
AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
) + fadeIn(animationSpec = fadeEffect)
},
popExitTransition = {
slideOutOfContainer(
AnimatedContentScope.SlideDirection.Right,
animationSpec = slideEffect
AnimatedContentScope.SlideDirection.Right, animationSpec = slideEffect
) + fadeOut(animationSpec = fadeEffect)
},
) { navBackStackEntry ->
spp.PageEvent(navBackStackEntry.arguments)
spp.Page(navBackStackEntry.arguments)
}
) { navBackStackEntry -> spp.PageWithEvent(navBackStackEntry.arguments) }
}
}
}

View File

@@ -185,6 +185,8 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings
private var sliceDataFn: SliceDataGetter = { _: Uri, _: Bundle? -> null }
fun build(): SettingsEntry {
val page = fromPage ?: owner
val isEnabled = page.isEnabled()
return SettingsEntry(
id = id(),
name = name,
@@ -196,10 +198,10 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings
toPage = toPage,
// attributes
isAllowSearch = isAllowSearch,
isAllowSearch = isEnabled && isAllowSearch,
isSearchDataDynamic = isSearchDataDynamic,
hasMutableStatus = hasMutableStatus,
hasSliceSupport = hasSliceSupport,
hasSliceSupport = isEnabled && hasSliceSupport,
// functions
statusDataImpl = statusDataFn,

View File

@@ -94,6 +94,12 @@ data class SettingsPage(
return !isCreateBy(NULL_PAGE_NAME) &&
!hasRuntimeParam()
}
fun isEnabled(): Boolean {
if (!SpaEnvironmentFactory.isReady()) return false
val pageProviderRepository by SpaEnvironmentFactory.instance.pageProviderRepository
return pageProviderRepository.getProviderOrNull(sppName)?.isEnabled(arguments) ?: false
}
}
fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {

View File

@@ -37,6 +37,14 @@ interface SettingsPageProvider {
val parameter: List<NamedNavArgument>
get() = emptyList()
/**
* The API to indicate whether the page is enabled or not.
* During SPA page migration, one can use it to enable certain pages in one release.
* When the page is disabled, all its related functionalities, such as browsing, search,
* slice provider, are disabled as well.
*/
fun isEnabled(arguments: Bundle?): Boolean = true
fun getTitle(arguments: Bundle?): String = displayName
fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()

View File

@@ -33,13 +33,15 @@ import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.compose.NavControllerWrapper
@Composable
internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
internal fun SettingsPageProvider.PageWithEvent(arguments: Bundle? = null) {
if (!isEnabled(arguments)) return
val page = remember(arguments) { createSettingsPage(arguments) }
val navController = LocalNavController.current
LifecycleEffect(
onStart = { page.logPageEvent(LogEvent.PAGE_ENTER, navController) },
onStop = { page.logPageEvent(LogEvent.PAGE_LEAVE, navController) },
)
Page(arguments)
}
private fun SettingsPage.logPageEvent(event: LogEvent, navController: NavControllerWrapper) {

View File

@@ -30,6 +30,7 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
import com.android.settingslib.spa.tests.testutils.SppDisabled
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.testutils.waitUntil
import com.google.common.truth.Truth
@@ -46,12 +47,12 @@ class BrowseActivityTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaLogger = SpaLoggerForTest()
private val spaEnvironment =
SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
@Test
fun testBrowsePage() {
spaLogger.reset()
val spaEnvironment =
SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
SpaEnvironmentFactory.reset(spaEnvironment)
val sppRepository by spaEnvironment.pageProviderRepository
@@ -75,6 +76,24 @@ class BrowseActivityTest {
spaLogger.verifyPageEvent(pageHome.id, 1, 1)
spaLogger.verifyPageEvent(pageLayer1.id, 1, 0)
}
@Test
fun testBrowseDisabledPage() {
spaLogger.reset()
val spaEnvironment = SpaEnvironmentForTest(
context, listOf(SppDisabled.createSettingsPage()), logger = spaLogger
)
SpaEnvironmentFactory.reset(spaEnvironment)
val sppRepository by spaEnvironment.pageProviderRepository
val sppDisabled = sppRepository.getProviderOrNull("SppDisabled")!!
val pageDisabled = sppDisabled.createSettingsPage()
composeTestRule.setContent { BrowseContent(sppRepository) }
composeTestRule.onNodeWithText(sppDisabled.getTitle(null)).assertDoesNotExist()
spaLogger.verifyPageEvent(pageDisabled.id, 0, 0)
}
}
private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {

View File

@@ -16,13 +16,16 @@
package com.android.settingslib.spa.framework.common
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.os.bundleOf
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.slice.appendSpaParams
import com.android.settingslib.spa.slice.getEntryId
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
import com.android.settingslib.spa.tests.testutils.getUniquePageId
import com.google.common.truth.Truth.assertThat
@@ -53,6 +56,9 @@ class MacroForTest(private val pageId: String, private val entryId: String) : En
@RunWith(AndroidJUnit4::class)
class SettingsEntryTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaEnvironment = SpaEnvironmentForTest(context)
@get:Rule
val composeTestRule = createComposeRule()
@@ -77,15 +83,15 @@ class SettingsEntryTest {
val owner = SettingsPage.create("mySpp")
val fromPage = SettingsPage.create("fromSpp")
val toPage = SettingsPage.create("toSpp")
val entryFrom = SettingsEntryBuilder.createLinkFrom("myEntry", owner)
.setLink(toPage = toPage).build()
val entryFrom =
SettingsEntryBuilder.createLinkFrom("myEntry", owner).setLink(toPage = toPage).build()
assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
assertThat(entryFrom.displayName).isEqualTo("myEntry")
assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
val entryTo = SettingsEntryBuilder.createLinkTo("myEntry", owner)
.setLink(fromPage = fromPage).build()
val entryTo =
SettingsEntryBuilder.createLinkTo("myEntry", owner).setLink(fromPage = fromPage).build()
assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
assertThat(entryTo.displayName).isEqualTo("myEntry")
assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
@@ -98,9 +104,7 @@ class SettingsEntryTest {
val entryInject = SettingsEntryBuilder.createInject(owner).build()
assertThat(entryInject.id).isEqualTo(
getUniqueEntryId(
INJECT_ENTRY_NAME_TEST,
owner,
toPage = owner
INJECT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
assertThat(entryInject.displayName).isEqualTo("${INJECT_ENTRY_NAME_TEST}_mySpp")
@@ -114,9 +118,7 @@ class SettingsEntryTest {
val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
assertThat(entryInject.id).isEqualTo(
getUniqueEntryId(
ROOT_ENTRY_NAME_TEST,
owner,
toPage = owner
ROOT_ENTRY_NAME_TEST, owner, toPage = owner
)
)
assertThat(entryInject.displayName).isEqualTo("myRootEntry")
@@ -126,13 +128,15 @@ class SettingsEntryTest {
@Test
fun testSetAttributes() {
val owner = SettingsPage.create("mySpp")
val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
.setDisplayName("myEntryDisplay")
.setIsSearchDataDynamic(false)
.setHasMutableStatus(true)
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
SpaEnvironmentFactory.reset(spaEnvironment)
val owner = SettingsPage.create("SppHome")
val entryBuilder =
SettingsEntryBuilder.create(owner, "myEntry")
.setDisplayName("myEntryDisplay")
.setIsSearchDataDynamic(false)
.setHasMutableStatus(true)
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entry = entryBuilder.build()
assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
assertThat(entry.displayName).isEqualTo("myEntryDisplay")
@@ -143,21 +147,52 @@ class SettingsEntryTest {
assertThat(entry.hasMutableStatus).isTrue()
assertThat(entry.hasSliceSupport).isTrue()
// Test disabled Spp
val ownerDisabled = SettingsPage.create("SppDisabled")
val entryBuilderDisabled =
SettingsEntryBuilder.create(ownerDisabled, "myEntry")
.setDisplayName("myEntryDisplay")
.setIsSearchDataDynamic(false)
.setHasMutableStatus(true)
.setSearchDataFn { null }
.setSliceDataFn { _, _ -> null }
val entryDisabled = entryBuilderDisabled.build()
assertThat(entryDisabled.id).isEqualTo(getUniqueEntryId("myEntry", ownerDisabled))
assertThat(entryDisabled.displayName).isEqualTo("myEntryDisplay")
assertThat(entryDisabled.fromPage).isNull()
assertThat(entryDisabled.toPage).isNull()
assertThat(entryDisabled.isAllowSearch).isFalse()
assertThat(entryDisabled.isSearchDataDynamic).isFalse()
assertThat(entryDisabled.hasMutableStatus).isTrue()
assertThat(entryDisabled.hasSliceSupport).isFalse()
// Clear search data fn
val entry2 = entryBuilder.clearSearchDataFn().build()
assertThat(entry2.isAllowSearch).isFalse()
// Clear SppHome in spa environment
SpaEnvironmentFactory.reset()
val entry3 = entryBuilder.build()
assertThat(entry3.id).isEqualTo(getUniqueEntryId("myEntry", owner))
assertThat(entry3.displayName).isEqualTo("myEntryDisplay")
assertThat(entry3.fromPage).isNull()
assertThat(entry3.toPage).isNull()
assertThat(entry3.isAllowSearch).isFalse()
assertThat(entry3.isSearchDataDynamic).isFalse()
assertThat(entry3.hasMutableStatus).isTrue()
assertThat(entry3.hasSliceSupport).isFalse()
}
@Test
fun testSetMarco() {
val owner = SettingsPage.create("mySpp", arguments = bundleOf("param" to "v1"))
val entry = SettingsEntryBuilder.create(owner, "myEntry")
.setMacro {
assertThat(it?.getString("param")).isEqualTo("v1")
assertThat(it?.getString("rtParam")).isEqualTo("v2")
assertThat(it?.getString("unknown")).isNull()
MacroForTest(getUniquePageId("mySpp"), getUniqueEntryId("myEntry", owner))
}
.build()
SpaEnvironmentFactory.reset(spaEnvironment)
val owner = SettingsPage.create("SppHome", arguments = bundleOf("param" to "v1"))
val entry = SettingsEntryBuilder.create(owner, "myEntry").setMacro {
assertThat(it?.getString("param")).isEqualTo("v1")
assertThat(it?.getString("rtParam")).isEqualTo("v2")
assertThat(it?.getString("unknown")).isNull()
MacroForTest(getUniquePageId("SppHome"), getUniqueEntryId("myEntry", owner))
}.build()
val rtArguments = bundleOf("rtParam" to "v2")
composeTestRule.setContent { entry.UiLayout(rtArguments) }
@@ -175,14 +210,14 @@ class SettingsEntryTest {
@Test
fun testSetSliceDataFn() {
val owner = SettingsPage.create("mySpp")
SpaEnvironmentFactory.reset(spaEnvironment)
val owner = SettingsPage.create("SppHome")
val entryId = getUniqueEntryId("myEntry", owner)
val emptySliceData = EntrySliceData()
val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
.setSliceDataFn { uri, _ ->
return@setSliceDataFn if (uri.getEntryId() == entryId) emptySliceData else null
}
val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry").setSliceDataFn { uri, _ ->
return@setSliceDataFn if (uri.getEntryId() == entryId) emptySliceData else null
}
val entry = entryBuilder.build()
assertThat(entry.id).isEqualTo(entryId)
assertThat(entry.hasSliceSupport).isTrue()

View File

@@ -23,6 +23,7 @@ import androidx.lifecycle.Observer
import androidx.slice.Slice
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SppHome
@@ -44,6 +45,8 @@ class SettingsSliceDataRepositoryTest {
@Test
fun getOrBuildSliceDataTest() {
SpaEnvironmentFactory.reset(spaEnvironment)
// Slice empty
assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
@@ -67,6 +70,8 @@ class SettingsSliceDataRepositoryTest {
@Test
fun getActiveSliceDataTest() {
SpaEnvironmentFactory.reset(spaEnvironment)
val page = SppLayer2.createSettingsPage()
val entryId = getUniqueEntryId("Layer2Entry1", page)
val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()

View File

@@ -92,6 +92,23 @@ object SppHome : SettingsPageProvider {
}
}
object SppDisabled : SettingsPageProvider {
override val name = "SppDisabled"
override fun isEnabled(arguments: Bundle?): Boolean = false
override fun getTitle(arguments: Bundle?): String {
return "TitleDisabled"
}
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val owner = this.createSettingsPage()
return listOf(
SppLayer1.buildInject().setLink(fromPage = owner).build(),
)
}
}
object SppLayer1 : SettingsPageProvider {
override val name = "SppLayer1"
@@ -190,7 +207,7 @@ class SpaEnvironmentForTest(
SettingsPageProviderRepository(
listOf(
SppHome, SppLayer1, SppLayer2,
SppForSearch,
SppForSearch, SppDisabled,
object : SettingsPageProvider {
override val name = "SppWithParam"
override val parameter = listOf(