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