1 /* <lambda>null2 * Copyright (C) 2023 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.settingslib.spa.debug 18 19 import android.net.Uri 20 import android.os.Bundle 21 import androidx.activity.ComponentActivity 22 import androidx.activity.compose.setContent 23 import androidx.compose.material3.Text 24 import androidx.compose.runtime.Composable 25 import androidx.compose.runtime.CompositionLocalProvider 26 import androidx.compose.runtime.remember 27 import androidx.compose.ui.platform.LocalContext 28 import androidx.navigation.NavType 29 import androidx.navigation.compose.NavHost 30 import androidx.navigation.compose.composable 31 import androidx.navigation.compose.rememberNavController 32 import androidx.navigation.navArgument 33 import com.android.settingslib.spa.R 34 import com.android.settingslib.spa.framework.common.LogCategory 35 import com.android.settingslib.spa.framework.common.SettingsEntry 36 import com.android.settingslib.spa.framework.common.SettingsPage 37 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory 38 import com.android.settingslib.spa.framework.compose.localNavController 39 import com.android.settingslib.spa.framework.compose.navigator 40 import com.android.settingslib.spa.framework.theme.SettingsTheme 41 import com.android.settingslib.spa.framework.util.SESSION_BROWSE 42 import com.android.settingslib.spa.framework.util.SESSION_SEARCH 43 import com.android.settingslib.spa.framework.util.createIntent 44 import com.android.settingslib.spa.slice.fromEntry 45 import com.android.settingslib.spa.slice.presenter.SliceDemo 46 import com.android.settingslib.spa.widget.preference.Preference 47 import com.android.settingslib.spa.widget.preference.PreferenceModel 48 import com.android.settingslib.spa.widget.scaffold.HomeScaffold 49 import com.android.settingslib.spa.widget.scaffold.RegularScaffold 50 51 private const val TAG = "DebugActivity" 52 private const val ROUTE_ROOT = "root" 53 private const val ROUTE_All_PAGES = "pages" 54 private const val ROUTE_All_ENTRIES = "entries" 55 private const val ROUTE_All_SLICES = "slices" 56 private const val ROUTE_PAGE = "page" 57 private const val ROUTE_ENTRY = "entry" 58 private const val PARAM_NAME_PAGE_ID = "pid" 59 private const val PARAM_NAME_ENTRY_ID = "eid" 60 61 /** 62 * The Debug Activity to display all Spa Pages & Entries. 63 * One can open the debug activity by: 64 * $ adb shell am start -n <Package>/com.android.settingslib.spa.debug.DebugActivity 65 * For gallery, Package = com.android.settingslib.spa.gallery 66 */ 67 class DebugActivity : ComponentActivity() { 68 private val spaEnvironment get() = SpaEnvironmentFactory.instance 69 70 override fun onCreate(savedInstanceState: Bundle?) { 71 setTheme(R.style.Theme_SpaLib) 72 super.onCreate(savedInstanceState) 73 spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK) 74 75 setContent { 76 SettingsTheme { 77 MainContent() 78 } 79 } 80 } 81 82 @Composable 83 private fun MainContent() { 84 val navController = rememberNavController() 85 CompositionLocalProvider(navController.localNavController()) { 86 NavHost(navController, ROUTE_ROOT) { 87 composable(route = ROUTE_ROOT) { RootPage() } 88 composable(route = ROUTE_All_PAGES) { AllPages() } 89 composable(route = ROUTE_All_ENTRIES) { AllEntries() } 90 composable(route = ROUTE_All_SLICES) { AllSlices() } 91 composable( 92 route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}", 93 arguments = listOf( 94 navArgument(PARAM_NAME_PAGE_ID) { type = NavType.StringType }, 95 ) 96 ) { navBackStackEntry -> OnePage(navBackStackEntry.arguments) } 97 composable( 98 route = "$ROUTE_ENTRY/{$PARAM_NAME_ENTRY_ID}", 99 arguments = listOf( 100 navArgument(PARAM_NAME_ENTRY_ID) { type = NavType.StringType }, 101 ) 102 ) { navBackStackEntry -> OneEntry(navBackStackEntry.arguments) } 103 } 104 } 105 } 106 107 @Composable 108 fun RootPage() { 109 val entryRepository by spaEnvironment.entryRepository 110 val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() } 111 val allEntry = remember { entryRepository.getAllEntries() } 112 val allSliceEntry = 113 remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } } 114 HomeScaffold(title = "Settings Debug") { 115 Preference(object : PreferenceModel { 116 override val title = "List All Pages (${allPageWithEntry.size})" 117 override val onClick = navigator(route = ROUTE_All_PAGES) 118 }) 119 Preference(object : PreferenceModel { 120 override val title = "List All Entries (${allEntry.size})" 121 override val onClick = navigator(route = ROUTE_All_ENTRIES) 122 }) 123 Preference(object : PreferenceModel { 124 override val title = "List All Slices (${allSliceEntry.size})" 125 override val onClick = navigator(route = ROUTE_All_SLICES) 126 }) 127 } 128 } 129 130 @Composable 131 fun AllPages() { 132 val entryRepository by spaEnvironment.entryRepository 133 val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() } 134 RegularScaffold(title = "All Pages (${allPageWithEntry.size})") { 135 for (pageWithEntry in allPageWithEntry) { 136 val page = pageWithEntry.page 137 Preference(object : PreferenceModel { 138 override val title = "${page.debugBrief()} (${pageWithEntry.entries.size})" 139 override val summary = { page.debugArguments() } 140 override val onClick = navigator(route = ROUTE_PAGE + "/${page.id}") 141 }) 142 } 143 } 144 } 145 146 @Composable 147 fun AllEntries() { 148 val entryRepository by spaEnvironment.entryRepository 149 val allEntry = remember { entryRepository.getAllEntries() } 150 RegularScaffold(title = "All Entries (${allEntry.size})") { 151 EntryList(allEntry) 152 } 153 } 154 155 @Composable 156 fun AllSlices() { 157 val entryRepository by spaEnvironment.entryRepository 158 val authority = spaEnvironment.sliceProviderAuthorities 159 val allSliceEntry = 160 remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } } 161 RegularScaffold(title = "All Slices (${allSliceEntry.size})") { 162 for (entry in allSliceEntry) { 163 SliceDemo(sliceUri = Uri.Builder().fromEntry(entry, authority).build()) 164 } 165 } 166 } 167 168 @Composable 169 fun OnePage(arguments: Bundle?) { 170 val entryRepository by spaEnvironment.entryRepository 171 val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "") 172 val pageWithEntry = entryRepository.getPageWithEntry(id)!! 173 val page = pageWithEntry.page 174 RegularScaffold(title = "Page - ${page.debugBrief()}") { 175 Text(text = "id = ${page.id}") 176 Text(text = page.debugArguments()) 177 Text(text = "enabled = ${page.isEnabled()}") 178 Text(text = "Entry size: ${pageWithEntry.entries.size}") 179 Preference(model = object : PreferenceModel { 180 override val title = "open page" 181 override val enabled = { 182 spaEnvironment.browseActivityClass != null && page.isBrowsable() 183 } 184 override val onClick = openPage(page) 185 }) 186 EntryList(pageWithEntry.entries) 187 } 188 } 189 190 @Composable 191 fun OneEntry(arguments: Bundle?) { 192 val entryRepository by spaEnvironment.entryRepository 193 val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "") 194 val entry = entryRepository.getEntry(id)!! 195 val entryContent = remember { entry.debugContent(entryRepository) } 196 RegularScaffold(title = "Entry - ${entry.debugBrief()}") { 197 Preference(model = object : PreferenceModel { 198 override val title = "open entry" 199 override val enabled = { 200 spaEnvironment.browseActivityClass != null && 201 entry.containerPage().isBrowsable() 202 } 203 override val onClick = openEntry(entry) 204 }) 205 Text(text = entryContent) 206 } 207 } 208 209 @Composable 210 private fun EntryList(entries: Collection<SettingsEntry>) { 211 for (entry in entries) { 212 Preference(object : PreferenceModel { 213 override val title = entry.debugBrief() 214 override val summary = { 215 "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}" 216 } 217 override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}") 218 }) 219 } 220 } 221 222 @Composable 223 private fun openPage(page: SettingsPage): (() -> Unit)? { 224 val context = LocalContext.current 225 val intent = 226 page.createIntent(SESSION_BROWSE) ?: return null 227 val route = page.buildRoute() 228 return { 229 spaEnvironment.logger.message( 230 TAG, "OpenPage: $route", category = LogCategory.FRAMEWORK 231 ) 232 context.startActivity(intent) 233 } 234 } 235 236 @Composable 237 private fun openEntry(entry: SettingsEntry): (() -> Unit)? { 238 val context = LocalContext.current 239 val intent = entry.createIntent(SESSION_SEARCH) 240 ?: return null 241 val route = entry.containerPage().buildRoute() 242 return { 243 spaEnvironment.logger.message( 244 TAG, "OpenEntry: $route", category = LogCategory.FRAMEWORK 245 ) 246 context.startActivity(intent) 247 } 248 } 249 } 250 251 /** 252 * A blank activity without any page. 253 */ 254 class BlankActivity : ComponentActivity() 255