diff --git a/src/com/android/settings/print/PrintSettingsPageProvider.kt b/src/com/android/settings/print/PrintSettingsPageProvider.kt index aac0a5d0cf4..f28f0bcd6f4 100644 --- a/src/com/android/settings/print/PrintSettingsPageProvider.kt +++ b/src/com/android/settings/print/PrintSettingsPageProvider.kt @@ -17,16 +17,32 @@ package com.android.settings.print import android.app.settings.SettingsEnums +import android.content.Context +import android.content.Intent +import android.net.Uri import android.os.Bundle +import android.provider.Settings import androidx.annotation.VisibleForTesting 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.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.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.lifecycle.compose.collectAsStateWithLifecycle 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_TITLE 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.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.PreferenceModel import com.android.settingslib.spa.widget.scaffold.RegularScaffold 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 kotlinx.coroutines.flow.Flow object PrintSettingsPageProvider : SettingsPageProvider { override val name = "PrintSettings" @@ -52,51 +73,101 @@ object PrintSettingsPageProvider : SettingsPageProvider { RegularScaffold(title = stringResource(R.string.print_settings)) { val context = LocalContext.current val printRepository = remember(context) { PrintRepository(context) } - UserProfilePager { - PrintServices(printRepository) - } + UserProfilePager { PrintServices(printRepository) } } } @Composable private fun PrintServices(printRepository: PrintRepository) { - val printServiceDisplayInfos by remember { - printRepository.printServiceDisplayInfosFlow() - }.collectAsStateWithLifecycle(initialValue = emptyList()) - Category(title = stringResource(R.string.print_settings_title)) { - for (printServiceDisplayInfo in printServiceDisplayInfos) { - PrintService(printServiceDisplayInfo) + val printServiceDisplayInfos by + remember { printRepository.printServiceDisplayInfosFlow() } + .collectAsStateWithLifecycle(initialValue = emptyList()) + if (printServiceDisplayInfos.isEmpty()) { + NoServicesInstalled() + } else { + Category(title = stringResource(R.string.print_settings_title)) { + for (printServiceDisplayInfo in printServiceDisplayInfos) { + 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 @Composable fun PrintService(displayInfo: PrintServiceDisplayInfo) { val context = LocalContext.current - Preference(model = object : PreferenceModel { - override val title = displayInfo.title - override val summary = { displayInfo.summary } - override val icon: @Composable () -> Unit = { - Image( - painter = rememberDrawablePainter(displayInfo.icon), - contentDescription = null, - modifier = Modifier.size(SettingsDimension.appIconItemSize), - ) - } - override val onClick = { - SubSettingLauncher(context).apply { - setDestination(PrintServiceSettingsFragment::class.qualifiedName) - setArguments( - bundleOf( - EXTRA_CHECKED to displayInfo.isEnabled, - EXTRA_TITLE to displayInfo.title, - EXTRA_SERVICE_COMPONENT_NAME to displayInfo.componentName - ) + Preference( + object : PreferenceModel { + override val title = displayInfo.title + override val summary = { displayInfo.summary } + override val icon: @Composable () -> Unit = { + Image( + painter = rememberDrawablePainter(displayInfo.icon), + contentDescription = null, + modifier = Modifier.size(SettingsDimension.appIconItemSize), ) - setSourceMetricsCategory(SettingsEnums.PRINT_SETTINGS) - }.launch() + } + override val onClick = { launchPrintServiceSettings(context, displayInfo) } } - }) + ) + } + + private fun launchPrintServiceSettings(context: Context, displayInfo: PrintServiceDisplayInfo) { + SubSettingLauncher(context) + .apply { + setDestination(PrintServiceSettingsFragment::class.qualifiedName) + setArguments( + bundleOf( + EXTRA_CHECKED to displayInfo.isEnabled, + EXTRA_TITLE to displayInfo.title, + EXTRA_SERVICE_COMPONENT_NAME to displayInfo.componentName + ) + ) + setSourceMetricsCategory(SettingsEnums.PRINT_SETTINGS) + } + .launch() + } + + @Composable + fun AddPrintService( + searchUriFlow: Flow = 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))) + } + } + ) } } diff --git a/tests/spa_unit/src/com/android/settings/print/PrintSettingsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/print/PrintSettingsPageProviderTest.kt index 746816b52c8..25714063d15 100644 --- a/tests/spa_unit/src/com/android/settings/print/PrintSettingsPageProviderTest.kt +++ b/tests/spa_unit/src/com/android/settings/print/PrintSettingsPageProviderTest.kt @@ -17,6 +17,7 @@ package com.android.settings.print import android.content.Context +import android.net.Uri import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext 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_SERVICE_COMPONENT_NAME import com.android.settings.print.PrintSettingsFragment.EXTRA_TITLE +import com.android.settings.print.PrintSettingsPageProvider.AddPrintService import com.android.settings.print.PrintSettingsPageProvider.PrintService +import kotlinx.coroutines.flow.flowOf import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -44,35 +47,32 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class PrintSettingsPageProviderTest { - @get:Rule - val composeTestRule = createComposeRule() + @get:Rule val composeTestRule = createComposeRule() - private val context: Context = spy(ApplicationProvider.getApplicationContext()) { - doNothing().whenever(mock).startActivity(any()) - } + private val context: Context = + spy(ApplicationProvider.getApplicationContext()) { + doNothing().whenever(mock).startActivity(any()) + } - private val displayInfo = PrintServiceDisplayInfo( - title = TITLE, - isEnabled = true, - summary = SUMMARY, - icon = context.getDrawable(R.drawable.ic_settings_print)!!, - componentName = "ComponentName", - ) + private val displayInfo = + PrintServiceDisplayInfo( + title = TITLE, + isEnabled = true, + summary = SUMMARY, + icon = context.getDrawable(R.drawable.ic_settings_print)!!, + componentName = "ComponentName", + ) @Test fun printService_titleDisplayed() { - composeTestRule.setContent { - PrintService(displayInfo) - } + composeTestRule.setContent { PrintService(displayInfo) } composeTestRule.onNodeWithText(TITLE).isDisplayed() } @Test fun printService_summaryDisplayed() { - composeTestRule.setContent { - PrintService(displayInfo) - } + composeTestRule.setContent { PrintService(displayInfo) } composeTestRule.onNodeWithText(SUMMARY).isDisplayed() } @@ -80,25 +80,43 @@ class PrintSettingsPageProviderTest { @Test fun printService_onClick() { composeTestRule.setContent { - CompositionLocalProvider(LocalContext provides context) { - PrintService(displayInfo) - } + CompositionLocalProvider(LocalContext provides context) { PrintService(displayInfo) } } composeTestRule.onNodeWithText(TITLE).performClick() - verify(context).startActivity(argThat { - val fragment = getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT) - val arguments = getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)!! - fragment == PrintServiceSettingsFragment::class.qualifiedName && - arguments.getBoolean(EXTRA_CHECKED) == displayInfo.isEnabled && - arguments.getString(EXTRA_TITLE) == displayInfo.title && - arguments.getString(EXTRA_SERVICE_COMPONENT_NAME) == displayInfo.componentName - }) + verify(context) + .startActivity( + argThat { + val fragment = getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT) + val arguments = getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)!! + fragment == PrintServiceSettingsFragment::class.qualifiedName && + arguments.getBoolean(EXTRA_CHECKED) == displayInfo.isEnabled && + arguments.getString(EXTRA_TITLE) == displayInfo.title && + 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 { const val TITLE = "Title" const val SUMMARY = "Summary" + const val SEARCH_URI = "search.uri" } }