1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.print 18 19 import android.app.settings.SettingsEnums 20 import android.content.Context 21 import android.content.Intent 22 import android.net.Uri 23 import android.os.Bundle 24 import android.provider.Settings 25 import androidx.annotation.VisibleForTesting 26 import androidx.compose.foundation.Image 27 import androidx.compose.foundation.layout.Column 28 import androidx.compose.foundation.layout.fillMaxSize 29 import androidx.compose.foundation.layout.padding 30 import androidx.compose.foundation.layout.size 31 import androidx.compose.material.icons.Icons 32 import androidx.compose.material.icons.outlined.Add 33 import androidx.compose.material.icons.outlined.Print 34 import androidx.compose.material3.Icon 35 import androidx.compose.material3.MaterialTheme 36 import androidx.compose.material3.Text 37 import androidx.compose.runtime.Composable 38 import androidx.compose.runtime.getValue 39 import androidx.compose.runtime.remember 40 import androidx.compose.ui.Alignment 41 import androidx.compose.ui.Modifier 42 import androidx.compose.ui.draw.alpha 43 import androidx.compose.ui.platform.LocalContext 44 import androidx.compose.ui.res.stringResource 45 import androidx.compose.ui.unit.dp 46 import androidx.core.os.bundleOf 47 import androidx.lifecycle.compose.collectAsStateWithLifecycle 48 import com.android.settings.R 49 import com.android.settings.core.SubSettingLauncher 50 import com.android.settings.print.PrintRepository.PrintServiceDisplayInfo 51 import com.android.settings.print.PrintSettingsFragment.EXTRA_CHECKED 52 import com.android.settings.print.PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME 53 import com.android.settings.print.PrintSettingsFragment.EXTRA_TITLE 54 import com.android.settingslib.spa.framework.common.SettingsPageProvider 55 import com.android.settingslib.spa.framework.compose.rememberContext 56 import com.android.settingslib.spa.framework.compose.rememberDrawablePainter 57 import com.android.settingslib.spa.framework.theme.SettingsDimension 58 import com.android.settingslib.spa.framework.theme.SettingsOpacity 59 import com.android.settingslib.spa.widget.preference.Preference 60 import com.android.settingslib.spa.widget.preference.PreferenceModel 61 import com.android.settingslib.spa.widget.scaffold.RegularScaffold 62 import com.android.settingslib.spa.widget.ui.Category 63 import com.android.settingslib.spa.widget.ui.SettingsIcon 64 import com.android.settingslib.spaprivileged.settingsprovider.settingsSecureStringFlow 65 import com.android.settingslib.spaprivileged.template.common.UserProfilePager 66 import kotlinx.coroutines.flow.Flow 67 68 object PrintSettingsPageProvider : SettingsPageProvider { 69 override val name = "PrintSettings" 70 71 @Composable 72 override fun Page(arguments: Bundle?) { 73 RegularScaffold(title = stringResource(R.string.print_settings)) { 74 val context = LocalContext.current 75 val printRepository = remember(context) { PrintRepository(context) } 76 UserProfilePager { PrintServices(printRepository) } 77 } 78 } 79 80 @Composable 81 private fun PrintServices(printRepository: PrintRepository) { 82 val printServiceDisplayInfos by 83 remember { printRepository.printServiceDisplayInfosFlow() } 84 .collectAsStateWithLifecycle(initialValue = emptyList()) 85 if (printServiceDisplayInfos.isEmpty()) { 86 NoServicesInstalled() 87 } else { 88 Category(title = stringResource(R.string.print_settings_title)) { 89 for (printServiceDisplayInfo in printServiceDisplayInfos) { 90 PrintService(printServiceDisplayInfo) 91 } 92 } 93 } 94 AddPrintService() 95 } 96 97 @Composable 98 private fun NoServicesInstalled() { 99 Column( 100 modifier = Modifier.fillMaxSize().padding(SettingsDimension.itemPaddingAround), 101 horizontalAlignment = Alignment.CenterHorizontally, 102 ) { 103 Icon( 104 imageVector = Icons.Outlined.Print, 105 contentDescription = null, 106 modifier = 107 Modifier.size(110.dp) 108 .padding(SettingsDimension.itemPaddingAround) 109 .alpha(SettingsOpacity.SurfaceTone), 110 ) 111 Text( 112 text = stringResource(R.string.print_no_services_installed), 113 style = MaterialTheme.typography.titleLarge, 114 ) 115 } 116 } 117 118 @VisibleForTesting 119 @Composable 120 fun PrintService(displayInfo: PrintServiceDisplayInfo) { 121 val context = LocalContext.current 122 Preference( 123 object : PreferenceModel { 124 override val title = displayInfo.title 125 override val summary = { displayInfo.summary } 126 override val icon: @Composable () -> Unit = { 127 Image( 128 painter = rememberDrawablePainter(displayInfo.icon), 129 contentDescription = null, 130 modifier = Modifier.size(SettingsDimension.appIconItemSize), 131 ) 132 } 133 override val onClick = { launchPrintServiceSettings(context, displayInfo) } 134 } 135 ) 136 } 137 138 private fun launchPrintServiceSettings(context: Context, displayInfo: PrintServiceDisplayInfo) { 139 SubSettingLauncher(context) 140 .apply { 141 setDestination(PrintServiceSettingsFragment::class.qualifiedName) 142 setArguments( 143 bundleOf( 144 EXTRA_CHECKED to displayInfo.isEnabled, 145 EXTRA_TITLE to displayInfo.title, 146 EXTRA_SERVICE_COMPONENT_NAME to displayInfo.componentName 147 ) 148 ) 149 setSourceMetricsCategory(SettingsEnums.PRINT_SETTINGS) 150 } 151 .launch() 152 } 153 154 @Composable 155 fun AddPrintService( 156 searchUriFlow: Flow<String> = rememberContext { context -> 157 context.settingsSecureStringFlow(Settings.Secure.PRINT_SERVICE_SEARCH_URI) 158 }, 159 ) { 160 val context = LocalContext.current 161 val searchUri by searchUriFlow.collectAsStateWithLifecycle("") 162 if (searchUri.isEmpty()) return 163 Preference( 164 object : PreferenceModel { 165 override val title = stringResource(R.string.print_menu_item_add_service) 166 override val icon = @Composable { SettingsIcon(imageVector = Icons.Outlined.Add) } 167 override val onClick = { 168 context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(searchUri))) 169 } 170 } 171 ) 172 } 173 } 174