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 
32 /** A fragment that represents a generic subpage in Safety Center. */
33 @RequiresApi(UPSIDE_DOWN_CAKE)
34 class SafetyCenterSubpageFragment : SafetyCenterFragment() {
35 
36     private lateinit var sourceGroupId: String
37     private lateinit var subpageBrandChip: SafetyBrandChipPreference
38     private lateinit var subpageIllustration: SafetyIllustrationPreference
39     private lateinit var subpageIssueGroup: PreferenceGroup
40     private lateinit var subpageEntryGroup: PreferenceGroup
41     private lateinit var subpageFooter: FooterPreference
42 
onCreatePreferencesnull43     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
44         super.onCreatePreferences(savedInstanceState, rootKey)
45         setPreferencesFromResource(R.xml.safety_center_subpage, rootKey)
46         sourceGroupId = requireArguments().getString(SOURCE_GROUP_ID_KEY)!!
47 
48         subpageBrandChip = getPreferenceScreen().findPreference(BRAND_CHIP_KEY)!!
49         subpageIllustration = getPreferenceScreen().findPreference(ILLUSTRATION_KEY)!!
50         subpageIssueGroup = getPreferenceScreen().findPreference(ISSUE_GROUP_KEY)!!
51         subpageEntryGroup = getPreferenceScreen().findPreference(ENTRY_GROUP_KEY)!!
52         subpageFooter = getPreferenceScreen().findPreference(FOOTER_KEY)!!
53 
54         subpageBrandChip.setupListener(requireActivity(), safetyCenterSessionId)
55         setupIllustration()
56         setupFooter()
57 
58         prerenderCurrentSafetyCenterData()
59     }
60 
configureInteractionLoggernull61     override fun configureInteractionLogger() {
62         val logger = safetyCenterViewModel.interactionLogger
63         logger.sessionId = safetyCenterSessionId
64         logger.navigationSource = NavigationSource.fromIntent(requireActivity().getIntent())
65         logger.viewType = ViewType.SUBPAGE
66         logger.groupId = sourceGroupId
67     }
68 
onResumenull69     override fun onResume() {
70         super.onResume()
71         safetyCenterViewModel.pageOpen(sourceGroupId)
72     }
73 
renderSafetyCenterDatanull74     override fun renderSafetyCenterData(uiData: SafetyCenterUiData?) {
75         Log.v(TAG, "renderSafetyCenterEntryGroup called with $uiData")
76         val entryGroup = uiData?.getMatchingGroup(sourceGroupId)
77         if (entryGroup == null) {
78             Log.w(TAG, "$sourceGroupId doesn't match any of the existing SafetySourcesGroup IDs")
79             closeSubpage(requireActivity(), requireContext(), safetyCenterSessionId)
80             return
81         }
82 
83         requireActivity().setTitle(entryGroup.title)
84         updateSafetyCenterIssues(uiData)
85         updateSafetyCenterEntries(entryGroup)
86     }
87 
setupIllustrationnull88     private fun setupIllustration() {
89         val resName = "illustration_${SnakeCaseConverter.fromCamelCase(sourceGroupId)}"
90         val context = requireContext()
91         val drawable = SafetyCenterResourcesApk(context).getDrawableByName(resName, context.theme)
92         if (drawable == null) {
93             Log.w(TAG, "$sourceGroupId doesn't have any matching illustration")
94             subpageIllustration.setVisible(false)
95         }
96 
97         subpageIllustration.illustrationDrawable = drawable
98     }
99 
setupFooternull100     private fun setupFooter() {
101         val resName = "${SnakeCaseConverter.fromCamelCase(sourceGroupId)}_footer"
102         val footerText = SafetyCenterResourcesApk(requireContext()).getStringByName(resName)
103         if (footerText.isEmpty()) {
104             Log.w(TAG, "$sourceGroupId doesn't have any matching footer")
105             subpageFooter.setVisible(false)
106         }
107         // footer is ordered last by default
108         // in order to keep a spacer after the footer, footer needs to be the second from last
109         subpageFooter.setOrder(Int.MAX_VALUE - 2)
110         subpageFooter.setSummary(footerText)
111     }
112 
updateSafetyCenterIssuesnull113     private fun updateSafetyCenterIssues(uiData: SafetyCenterUiData?) {
114         subpageIssueGroup.removeAll()
115         val subpageIssues = uiData?.getMatchingIssues(sourceGroupId)
116         val subpageDismissedIssues = uiData?.getMatchingDismissedIssues(sourceGroupId)
117 
118         subpageIllustration.isVisible =
119             subpageIssues.isNullOrEmpty() && subpageIllustration.illustrationDrawable != null
120 
121         if (subpageIssues.isNullOrEmpty() && subpageDismissedIssues.isNullOrEmpty()) {
122             Log.w(TAG, "$sourceGroupId doesn't have any matching SafetyCenterIssues")
123             return
124         }
125 
126         collapsableIssuesCardHelper.addIssues(
127             requireContext(),
128             safetyCenterViewModel,
129             getChildFragmentManager(),
130             subpageIssueGroup,
131             subpageIssues,
132             subpageDismissedIssues,
133             uiData.resolvedIssues,
134             requireActivity().getTaskId()
135         )
136     }
137 
updateSafetyCenterEntriesnull138     private fun updateSafetyCenterEntries(entryGroup: SafetyCenterEntryGroup) {
139         Log.v(TAG, "updateSafetyCenterEntries called with $entryGroup")
140         subpageEntryGroup.removeAll()
141         for (entry in entryGroup.entries) {
142             subpageEntryGroup.addPreference(
143                 SafetySubpageEntryPreference(
144                     requireContext(),
145                     PendingIntentSender.getTaskIdForEntry(
146                         entry.id,
147                         sameTaskSourceIds,
148                         requireActivity()
149                     ),
150                     entry,
151                     safetyCenterViewModel
152                 )
153             )
154         }
155     }
156 
157     companion object {
158         private val TAG: String = SafetyCenterSubpageFragment::class.java.simpleName
159         private const val BRAND_CHIP_KEY: String = "subpage_brand_chip"
160         private const val ILLUSTRATION_KEY: String = "subpage_illustration"
161         private const val ISSUE_GROUP_KEY: String = "subpage_issue_group"
162         private const val ENTRY_GROUP_KEY: String = "subpage_entry_group"
163         private const val FOOTER_KEY: String = "subpage_footer"
164         private const val SOURCE_GROUP_ID_KEY: String = "source_group_id"
165 
166         /** Creates an instance of SafetyCenterSubpageFragment with the arguments set */
167         @JvmStatic
newInstancenull168         fun newInstance(sessionId: Long, groupId: String): SafetyCenterSubpageFragment {
169             val args = Bundle()
170             args.putLong(EXTRA_SESSION_ID, sessionId)
171             args.putString(SOURCE_GROUP_ID_KEY, groupId)
172 
173             val subpageFragment = SafetyCenterSubpageFragment()
174             subpageFragment.setArguments(args)
175             return subpageFragment
176         }
177     }
178 }
179