Refactor PrintSettings (2/n)

The "Add service" button.

Bug: 320076351
Flag: com.android.settings.flags.refactor_print_settings
Test: manual
Test: unit test
Change-Id: I624293edcbfe9ef8388d48759611aeac522352a4
This commit is contained in:
Chaohui Wang
2024-06-17 17:56:45 +08:00
parent 3576f4840d
commit 9e88efecdf
2 changed files with 149 additions and 60 deletions

View File

@@ -17,16 +17,32 @@
package com.android.settings.print package com.android.settings.print
import android.app.settings.SettingsEnums import android.app.settings.SettingsEnums
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Print
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R import com.android.settings.R
@@ -36,13 +52,18 @@ import com.android.settings.print.PrintSettingsFragment.EXTRA_CHECKED
import com.android.settings.print.PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME import com.android.settings.print.PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME
import com.android.settings.print.PrintSettingsFragment.EXTRA_TITLE import com.android.settings.print.PrintSettingsFragment.EXTRA_TITLE
import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.rememberContext
import com.android.settingslib.spa.framework.compose.rememberDrawablePainter import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity
import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Category import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spa.widget.ui.SettingsIcon
import com.android.settingslib.spaprivileged.settingsprovider.settingsSecureStringFlow
import com.android.settingslib.spaprivileged.template.common.UserProfilePager import com.android.settingslib.spaprivileged.template.common.UserProfilePager
import kotlinx.coroutines.flow.Flow
object PrintSettingsPageProvider : SettingsPageProvider { object PrintSettingsPageProvider : SettingsPageProvider {
override val name = "PrintSettings" override val name = "PrintSettings"
@@ -52,29 +73,54 @@ object PrintSettingsPageProvider : SettingsPageProvider {
RegularScaffold(title = stringResource(R.string.print_settings)) { RegularScaffold(title = stringResource(R.string.print_settings)) {
val context = LocalContext.current val context = LocalContext.current
val printRepository = remember(context) { PrintRepository(context) } val printRepository = remember(context) { PrintRepository(context) }
UserProfilePager { UserProfilePager { PrintServices(printRepository) }
PrintServices(printRepository)
}
} }
} }
@Composable @Composable
private fun PrintServices(printRepository: PrintRepository) { private fun PrintServices(printRepository: PrintRepository) {
val printServiceDisplayInfos by remember { val printServiceDisplayInfos by
printRepository.printServiceDisplayInfosFlow() remember { printRepository.printServiceDisplayInfosFlow() }
}.collectAsStateWithLifecycle(initialValue = emptyList()) .collectAsStateWithLifecycle(initialValue = emptyList())
if (printServiceDisplayInfos.isEmpty()) {
NoServicesInstalled()
} else {
Category(title = stringResource(R.string.print_settings_title)) { Category(title = stringResource(R.string.print_settings_title)) {
for (printServiceDisplayInfo in printServiceDisplayInfos) { for (printServiceDisplayInfo in printServiceDisplayInfos) {
PrintService(printServiceDisplayInfo) PrintService(printServiceDisplayInfo)
} }
} }
} }
AddPrintService()
}
@Composable
private fun NoServicesInstalled() {
Column(
modifier = Modifier.fillMaxSize().padding(SettingsDimension.itemPaddingAround),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
imageVector = Icons.Outlined.Print,
contentDescription = null,
modifier =
Modifier.size(110.dp)
.padding(SettingsDimension.itemPaddingAround)
.alpha(SettingsOpacity.SurfaceTone),
)
Text(
text = stringResource(R.string.print_no_services_installed),
style = MaterialTheme.typography.titleLarge,
)
}
}
@VisibleForTesting @VisibleForTesting
@Composable @Composable
fun PrintService(displayInfo: PrintServiceDisplayInfo) { fun PrintService(displayInfo: PrintServiceDisplayInfo) {
val context = LocalContext.current val context = LocalContext.current
Preference(model = object : PreferenceModel { Preference(
object : PreferenceModel {
override val title = displayInfo.title override val title = displayInfo.title
override val summary = { displayInfo.summary } override val summary = { displayInfo.summary }
override val icon: @Composable () -> Unit = { override val icon: @Composable () -> Unit = {
@@ -84,8 +130,14 @@ object PrintSettingsPageProvider : SettingsPageProvider {
modifier = Modifier.size(SettingsDimension.appIconItemSize), modifier = Modifier.size(SettingsDimension.appIconItemSize),
) )
} }
override val onClick = { override val onClick = { launchPrintServiceSettings(context, displayInfo) }
SubSettingLauncher(context).apply { }
)
}
private fun launchPrintServiceSettings(context: Context, displayInfo: PrintServiceDisplayInfo) {
SubSettingLauncher(context)
.apply {
setDestination(PrintServiceSettingsFragment::class.qualifiedName) setDestination(PrintServiceSettingsFragment::class.qualifiedName)
setArguments( setArguments(
bundleOf( bundleOf(
@@ -95,8 +147,27 @@ object PrintSettingsPageProvider : SettingsPageProvider {
) )
) )
setSourceMetricsCategory(SettingsEnums.PRINT_SETTINGS) setSourceMetricsCategory(SettingsEnums.PRINT_SETTINGS)
}.launch()
} }
}) .launch()
}
@Composable
fun AddPrintService(
searchUriFlow: Flow<String> = rememberContext { context ->
context.settingsSecureStringFlow(Settings.Secure.PRINT_SERVICE_SEARCH_URI)
},
) {
val context = LocalContext.current
val searchUri by searchUriFlow.collectAsStateWithLifecycle("")
if (searchUri.isEmpty()) return
Preference(
object : PreferenceModel {
override val title = stringResource(R.string.print_menu_item_add_service)
override val icon = @Composable { SettingsIcon(imageVector = Icons.Outlined.Add) }
override val onClick = {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)))
}
}
)
} }
} }

View File

@@ -17,6 +17,7 @@
package com.android.settings.print package com.android.settings.print
import android.content.Context import android.content.Context
import android.net.Uri
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.isDisplayed
@@ -31,7 +32,9 @@ import com.android.settings.print.PrintRepository.PrintServiceDisplayInfo
import com.android.settings.print.PrintSettingsFragment.EXTRA_CHECKED import com.android.settings.print.PrintSettingsFragment.EXTRA_CHECKED
import com.android.settings.print.PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME import com.android.settings.print.PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME
import com.android.settings.print.PrintSettingsFragment.EXTRA_TITLE import com.android.settings.print.PrintSettingsFragment.EXTRA_TITLE
import com.android.settings.print.PrintSettingsPageProvider.AddPrintService
import com.android.settings.print.PrintSettingsPageProvider.PrintService import com.android.settings.print.PrintSettingsPageProvider.PrintService
import kotlinx.coroutines.flow.flowOf
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -44,14 +47,15 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class PrintSettingsPageProviderTest { class PrintSettingsPageProviderTest {
@get:Rule @get:Rule val composeTestRule = createComposeRule()
val composeTestRule = createComposeRule()
private val context: Context = spy(ApplicationProvider.getApplicationContext()) { private val context: Context =
spy(ApplicationProvider.getApplicationContext()) {
doNothing().whenever(mock).startActivity(any()) doNothing().whenever(mock).startActivity(any())
} }
private val displayInfo = PrintServiceDisplayInfo( private val displayInfo =
PrintServiceDisplayInfo(
title = TITLE, title = TITLE,
isEnabled = true, isEnabled = true,
summary = SUMMARY, summary = SUMMARY,
@@ -61,18 +65,14 @@ class PrintSettingsPageProviderTest {
@Test @Test
fun printService_titleDisplayed() { fun printService_titleDisplayed() {
composeTestRule.setContent { composeTestRule.setContent { PrintService(displayInfo) }
PrintService(displayInfo)
}
composeTestRule.onNodeWithText(TITLE).isDisplayed() composeTestRule.onNodeWithText(TITLE).isDisplayed()
} }
@Test @Test
fun printService_summaryDisplayed() { fun printService_summaryDisplayed() {
composeTestRule.setContent { composeTestRule.setContent { PrintService(displayInfo) }
PrintService(displayInfo)
}
composeTestRule.onNodeWithText(SUMMARY).isDisplayed() composeTestRule.onNodeWithText(SUMMARY).isDisplayed()
} }
@@ -80,25 +80,43 @@ class PrintSettingsPageProviderTest {
@Test @Test
fun printService_onClick() { fun printService_onClick() {
composeTestRule.setContent { composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) { CompositionLocalProvider(LocalContext provides context) { PrintService(displayInfo) }
PrintService(displayInfo)
}
} }
composeTestRule.onNodeWithText(TITLE).performClick() composeTestRule.onNodeWithText(TITLE).performClick()
verify(context).startActivity(argThat { verify(context)
.startActivity(
argThat {
val fragment = getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT) val fragment = getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)
val arguments = getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)!! val arguments = getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)!!
fragment == PrintServiceSettingsFragment::class.qualifiedName && fragment == PrintServiceSettingsFragment::class.qualifiedName &&
arguments.getBoolean(EXTRA_CHECKED) == displayInfo.isEnabled && arguments.getBoolean(EXTRA_CHECKED) == displayInfo.isEnabled &&
arguments.getString(EXTRA_TITLE) == displayInfo.title && arguments.getString(EXTRA_TITLE) == displayInfo.title &&
arguments.getString(EXTRA_SERVICE_COMPONENT_NAME) == displayInfo.componentName arguments.getString(EXTRA_SERVICE_COMPONENT_NAME) ==
}) displayInfo.componentName
}
)
}
@Test
fun addPrintService_onClick() {
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
AddPrintService(flowOf(SEARCH_URI))
}
}
composeTestRule
.onNodeWithText(context.getString(R.string.print_menu_item_add_service))
.performClick()
verify(context).startActivity(argThat { data == Uri.parse(SEARCH_URI) })
} }
private companion object { private companion object {
const val TITLE = "Title" const val TITLE = "Title"
const val SUMMARY = "Summary" const val SUMMARY = "Summary"
const val SEARCH_URI = "search.uri"
} }
} }