• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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