• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.permissioncontroller.safetycenter.ui
18 
19 import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
20 import android.os.Bundle
21 import android.safetycenter.SafetyCenterEntryGroup
22 import android.util.Log
23 import androidx.annotation.RequiresApi
24 import androidx.preference.PreferenceGroup
25 import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
26 import com.android.permissioncontroller.R
27 import com.android.permissioncontroller.safetycenter.ui.SafetyBrandChipPreference.Companion.closeSubpage
28 import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
29 import com.android.safetycenter.resources.SafetyCenterResourcesApk
30 import com.android.settingslib.widget.FooterPreference
31 import com.android.settingslib.widget.SettingsThemeHelper
32 
33 /** A fragment that represents a generic subpage in Safety Center. */
34 @RequiresApi(UPSIDE_DOWN_CAKE)
35 class SafetyCenterSubpageFragment : SafetyCenterFragment() {
36 
37     private lateinit var sourceGroupId: String
38     private lateinit var subpageBrandChip: SafetyBrandChipPreference
39     private lateinit var subpageIllustration: SafetyIllustrationPreference
40     private lateinit var subpageIssueGroup: PreferenceGroup
41     private lateinit var subpageEntryGroup: PreferenceGroup
42     private lateinit var subpageFooter: FooterPreference
43 
onCreatePreferencesnull44     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
45         super.onCreatePreferences(savedInstanceState, rootKey)
46         setPreferencesFromResource(R.xml.safety_center_subpage, rootKey)
47         sourceGroupId = requireArguments().getString(SOURCE_GROUP_ID_KEY)!!
48 
49         subpageBrandChip = preferenceScreen.findPreference(BRAND_CHIP_KEY)!!
50         subpageIllustration = preferenceScreen.findPreference(ILLUSTRATION_KEY)!!
51         subpageIssueGroup = preferenceScreen.findPreference(ISSUE_GROUP_KEY)!!
52         subpageEntryGroup = preferenceScreen.findPreference(ENTRY_GROUP_KEY)!!
53         subpageFooter = preferenceScreen.findPreference(FOOTER_KEY)!!
54 
55         subpageBrandChip.setupListener(requireActivity(), safetyCenterSessionId)
56         setupIllustration()
57         setupFooter()
58         maybeRemoveSpacer()
59 
60         prerenderCurrentSafetyCenterData()
61     }
62 
configureInteractionLoggernull63     override fun configureInteractionLogger() {
64         val logger = safetyCenterViewModel.interactionLogger
65         logger.sessionId = safetyCenterSessionId
66         logger.navigationSource = NavigationSource.fromIntent(requireActivity().getIntent())
67         logger.viewType = ViewType.SUBPAGE
68         logger.groupId = sourceGroupId
69     }
70 
onResumenull71     override fun onResume() {
72         super.onResume()
73         safetyCenterViewModel.pageOpen(sourceGroupId)
74     }
75 
renderSafetyCenterDatanull76     override fun renderSafetyCenterData(uiData: SafetyCenterUiData?) {
77         Log.v(TAG, "renderSafetyCenterEntryGroup called with $uiData")
78         val entryGroup = uiData?.getMatchingGroup(sourceGroupId)
79         if (entryGroup == null) {
80             Log.w(TAG, "$sourceGroupId doesn't match any of the existing SafetySourcesGroup IDs")
81             closeSubpage(requireActivity(), requireContext(), safetyCenterSessionId)
82             return
83         }
84 
85         requireActivity().title = entryGroup.title
86         updateSafetyCenterIssues(uiData)
87         updateSafetyCenterEntries(entryGroup)
88     }
89 
setupIllustrationnull90     private fun setupIllustration() {
91         val resName = "illustration_${SnakeCaseConverter.fromCamelCase(sourceGroupId)}"
92         val context = requireContext()
93         val drawable = SafetyCenterResourcesApk(context).getDrawableByName(resName, context.theme)
94         if (drawable == null) {
95             Log.w(TAG, "$sourceGroupId doesn't have any matching illustration")
96             subpageIllustration.isVisible = false
97         }
98 
99         subpageIllustration.illustrationDrawable = drawable
100     }
101 
setupFooternull102     private fun setupFooter() {
103         val resName = "${SnakeCaseConverter.fromCamelCase(sourceGroupId)}_footer"
104         val footerText = SafetyCenterResourcesApk(requireContext()).getStringByName(resName)
105         if (footerText.isEmpty()) {
106             Log.w(TAG, "$sourceGroupId doesn't have any matching footer")
107             subpageFooter.isVisible = false
108         }
109         // footer is ordered last by default
110         // in order to keep a spacer after the footer, footer needs to be the second from last
111         subpageFooter.order = Int.MAX_VALUE - 2
112         subpageFooter.summary = footerText
113     }
114 
maybeRemoveSpacernull115     private fun maybeRemoveSpacer() {
116         if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
117             val spacerPreference = preferenceScreen.findPreference<SpacerPreference>(SPACER_KEY)!!
118             preferenceScreen.removePreference(spacerPreference)
119         }
120     }
121 
updateSafetyCenterIssuesnull122     private fun updateSafetyCenterIssues(uiData: SafetyCenterUiData?) {
123         subpageIssueGroup.removeAll()
124         val subpageIssues = uiData?.getMatchingIssues(sourceGroupId)
125         val subpageDismissedIssues = uiData?.getMatchingDismissedIssues(sourceGroupId)
126 
127         subpageIllustration.isVisible =
128             subpageIssues.isNullOrEmpty() && subpageIllustration.illustrationDrawable != null
129 
130         if (subpageIssues.isNullOrEmpty() && subpageDismissedIssues.isNullOrEmpty()) {
131             Log.w(TAG, "$sourceGroupId doesn't have any matching SafetyCenterIssues")
132             return
133         }
134 
135         collapsableIssuesCardHelper.addIssues(
136             requireContext(),
137             safetyCenterViewModel,
138             getChildFragmentManager(),
139             subpageIssueGroup,
140             subpageIssues,
141             subpageDismissedIssues,
142             uiData.resolvedIssues,
143             requireActivity().taskId,
144         )
145     }
146 
updateSafetyCenterEntriesnull147     private fun updateSafetyCenterEntries(entryGroup: SafetyCenterEntryGroup) {
148         Log.v(TAG, "updateSafetyCenterEntries called with $entryGroup")
149         subpageEntryGroup.removeAll()
150         for (entry in entryGroup.entries) {
151             subpageEntryGroup.addPreference(
152                 SafetySubpageEntryPreference(
153                     requireContext(),
154                     PendingIntentSender.getTaskIdForEntry(
155                         entry.id,
156                         sameTaskSourceIds,
157                         requireActivity(),
158                     ),
159                     entry,
160                     safetyCenterViewModel,
161                 )
162             )
163         }
164     }
165 
166     companion object {
167         private val TAG = SafetyCenterSubpageFragment::class.java.simpleName
168         private const val BRAND_CHIP_KEY = "subpage_brand_chip"
169         private const val ILLUSTRATION_KEY = "subpage_illustration"
170         private const val ISSUE_GROUP_KEY = "subpage_issue_group"
171         private const val ENTRY_GROUP_KEY = "subpage_entry_group"
172         private const val FOOTER_KEY = "subpage_footer"
173         private const val SPACER_KEY = "subpage_spacer"
174         private const val SOURCE_GROUP_ID_KEY = "source_group_id"
175 
176         /** Creates an instance of SafetyCenterSubpageFragment with the arguments set */
177         @JvmStatic
newInstancenull178         fun newInstance(sessionId: Long, groupId: String): SafetyCenterSubpageFragment {
179             val args = Bundle()
180             args.putLong(EXTRA_SESSION_ID, sessionId)
181             args.putString(SOURCE_GROUP_ID_KEY, groupId)
182 
183             val subpageFragment = SafetyCenterSubpageFragment()
184             subpageFragment.setArguments(args)
185             return subpageFragment
186         }
187     }
188 }
189