1 /*
2  * 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.framework.common
18 
19 import android.util.Log
20 import java.util.LinkedList
21 
22 private const val TAG = "EntryRepository"
23 private const val MAX_ENTRY_SIZE = 5000
24 private const val MAX_ENTRY_DEPTH = 10
25 
26 data class SettingsPageWithEntry(
27     val page: SettingsPage,
28     val entries: List<SettingsEntry>,
29     // The inject entry, which to-page is current page.
30     val injectEntry: SettingsEntry,
31 )
32 
33 /**
34  * The repository to maintain all Settings entries
35  */
36 class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
37     // Map of entry unique Id to entry
38     private val entryMap: Map<String, SettingsEntry>
39 
40     // Map of Settings page to its contained entries.
41     private val pageWithEntryMap: Map<String, SettingsPageWithEntry>
42 
43     init {
44         Log.d(TAG, "Initialize")
45         entryMap = mutableMapOf()
46         pageWithEntryMap = mutableMapOf()
47 
48         val nullPage = NullPageProvider.createSettingsPage()
49         val entryQueue = LinkedList<SettingsEntry>()
50         for (page in sppRepository.getAllRootPages()) {
51             val rootEntry =
52                 SettingsEntryBuilder.createRoot(owner = page).setLink(fromPage = nullPage).build()
53             if (!entryMap.containsKey(rootEntry.id)) {
54                 entryQueue.push(rootEntry)
55                 entryMap.put(rootEntry.id, rootEntry)
56             }
57         }
58 
59         while (entryQueue.isNotEmpty() && entryMap.size < MAX_ENTRY_SIZE) {
60             val entry = entryQueue.pop()
61             val page = entry.toPage
62             if (page == null || pageWithEntryMap.containsKey(page.id)) continue
63             val spp = sppRepository.getProviderOrNull(page.sppName) ?: continue
64             val newEntries = spp.buildEntry(page.arguments)
65             // The page id could be existed already, if there are 2+ pages go to the same one.
66             // For now, override the previous ones, which means only the last from-page is kept.
67             // TODO: support multiple from-pages if necessary.
68             pageWithEntryMap[page.id] = SettingsPageWithEntry(
69                 page = page,
70                 entries = newEntries,
71                 injectEntry = entry
72             )
73             for (newEntry in newEntries) {
74                 if (!entryMap.containsKey(newEntry.id)) {
75                     entryQueue.push(newEntry)
76                     entryMap.put(newEntry.id, newEntry)
77                 }
78             }
79         }
80 
81         Log.d(
82             TAG,
83             "Initialize Completed: ${entryMap.size} entries in ${pageWithEntryMap.size} pages"
84         )
85     }
86 
getAllPageWithEntrynull87     fun getAllPageWithEntry(): Collection<SettingsPageWithEntry> {
88         return pageWithEntryMap.values
89     }
90 
getPageWithEntrynull91     fun getPageWithEntry(pageId: String): SettingsPageWithEntry? {
92         return pageWithEntryMap[pageId]
93     }
94 
getAllEntriesnull95     fun getAllEntries(): Collection<SettingsEntry> {
96         return entryMap.values
97     }
98 
getEntrynull99     fun getEntry(entryId: String): SettingsEntry? {
100         return entryMap[entryId]
101     }
102 
getEntryPathnull103     private fun getEntryPath(entryId: String): List<SettingsEntry> {
104         val entryPath = ArrayList<SettingsEntry>()
105         var currentEntry = entryMap[entryId]
106         while (currentEntry != null && entryPath.size < MAX_ENTRY_DEPTH) {
107             entryPath.add(currentEntry)
108             val currentPage = currentEntry.containerPage()
109             currentEntry = pageWithEntryMap[currentPage.id]?.injectEntry
110         }
111         return entryPath
112     }
113 
getEntryPathWithLabelnull114     fun getEntryPathWithLabel(entryId: String): List<String> {
115         val entryPath = getEntryPath(entryId)
116         return entryPath.map { it.label }
117     }
118 
getEntryPathWithTitlenull119     fun getEntryPathWithTitle(entryId: String, defaultTitle: String): List<String> {
120         val entryPath = getEntryPath(entryId)
121         return entryPath.map {
122             if (it.toPage == null)
123                 defaultTitle
124             else {
125                 it.toPage.getTitle()
126             }
127         }
128     }
129 }
130