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