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.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.lifecycle.ViewModelProvider
25 import androidx.preference.Preference
26 import androidx.preference.PreferenceGroup
27 import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
28 import com.android.permissioncontroller.R
29 import com.android.permissioncontroller.safetycenter.SafetyCenterConstants.PRIVACY_SOURCES_GROUP_ID
30 import com.android.permissioncontroller.safetycenter.ui.SafetyBrandChipPreference.Companion.closeSubpage
31 import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel
32 import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel.Pref
33 import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel.PrefState
34 import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModelFactory
35 import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
36 import com.android.safetycenter.internaldata.SafetyCenterIds
37 
38 /** A fragment that represents the privacy subpage in Safety Center. */
39 @RequiresApi(UPSIDE_DOWN_CAKE)
40 class PrivacySubpageFragment : SafetyCenterFragment() {
41 
42     private lateinit var subpageBrandChip: SafetyBrandChipPreference
43     private lateinit var subpageIssueGroup: PreferenceGroup
44     private lateinit var subpageGenericEntryGroup: PreferenceGroup
45     private lateinit var subpageControlsExtraEntryGroup: PreferenceGroup
46     private lateinit var privacyControlsViewModel: PrivacyControlsViewModel
47 
48     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
49         super.onCreatePreferences(savedInstanceState, rootKey)
50         setPreferencesFromResource(R.xml.privacy_subpage, rootKey)
51 
52         subpageBrandChip = getPreferenceScreen().findPreference(BRAND_CHIP_KEY)!!
53         subpageIssueGroup = getPreferenceScreen().findPreference(ISSUE_GROUP_KEY)!!
54         subpageGenericEntryGroup = getPreferenceScreen().findPreference(GENERIC_ENTRY_GROUP_KEY)!!
55         subpageControlsExtraEntryGroup =
56             getPreferenceScreen().findPreference(CONTROLS_EXTRA_ENTRY_GROUP_KEY)!!
57         subpageBrandChip.setupListener(requireActivity(), safetyCenterSessionId)
58 
59         val factory = PrivacyControlsViewModelFactory(requireActivity().getApplication())
60         privacyControlsViewModel =
61             ViewModelProvider(this, factory).get(PrivacyControlsViewModel::class.java)
62         privacyControlsViewModel.controlStateLiveData.observe(this) {
63             prefStates: Map<Pref, PrefState> ->
64             renderPrivacyControls(prefStates)
65         }
66 
67         prerenderCurrentSafetyCenterData()
68     }
69 
70     override fun configureInteractionLogger() {
71         val logger = safetyCenterViewModel.interactionLogger
72         logger.sessionId = safetyCenterSessionId
73         logger.navigationSource = NavigationSource.fromIntent(requireActivity().getIntent())
74         logger.viewType = ViewType.SUBPAGE
75         logger.groupId = PRIVACY_SOURCES_GROUP_ID
76     }
77 
78     override fun onResume() {
79         super.onResume()
80         safetyCenterViewModel.pageOpen(PRIVACY_SOURCES_GROUP_ID)
81     }
82 
83     override fun renderSafetyCenterData(uiData: SafetyCenterUiData?) {
84         Log.v(TAG, "renderSafetyCenterEntryGroup called with $uiData")
85         val entryGroup = uiData?.getMatchingGroup(PRIVACY_SOURCES_GROUP_ID)
86         if (entryGroup == null) {
87             Log.w(
88                 TAG,
89                 "$PRIVACY_SOURCES_GROUP_ID doesn't match any of the existing SafetySourcesGroup IDs"
90             )
91             closeSubpage(requireActivity(), requireContext(), safetyCenterSessionId)
92             return
93         }
94 
95         requireActivity().setTitle(entryGroup.title)
96         updateSafetyCenterIssues(uiData)
97         updateSafetyCenterEntries(entryGroup)
98     }
99 
100     private fun updateSafetyCenterIssues(uiData: SafetyCenterUiData?) {
101         subpageIssueGroup.removeAll()
102         val subpageIssues = uiData?.getMatchingIssues(PRIVACY_SOURCES_GROUP_ID)
103         val subpageDismissedIssues = uiData?.getMatchingDismissedIssues(PRIVACY_SOURCES_GROUP_ID)
104         if (subpageIssues.isNullOrEmpty() && subpageDismissedIssues.isNullOrEmpty()) {
105             Log.w(TAG, "$PRIVACY_SOURCES_GROUP_ID doesn't have any matching SafetyCenterIssues")
106             return
107         }
108 
109         collapsableIssuesCardHelper.addIssues(
110             requireContext(),
111             safetyCenterViewModel,
112             getChildFragmentManager(),
113             subpageIssueGroup,
114             subpageIssues,
115             subpageDismissedIssues,
116             uiData.resolvedIssues,
117             requireActivity().getTaskId()
118         )
119     }
120 
121     private fun updateSafetyCenterEntries(entryGroup: SafetyCenterEntryGroup) {
122         Log.v(TAG, "updateSafetyCenterEntries called with $entryGroup")
123         subpageGenericEntryGroup.removeAll()
124         subpageControlsExtraEntryGroup.removeAll()
125 
126         for (entry in entryGroup.entries) {
127             val entryId = entry.id
128             val sourceId = SafetyCenterIds.entryIdFromString(entryId).getSafetySourceId()
129 
130             val subpageEntry =
131                 SafetySubpageEntryPreference(
132                     requireContext(),
133                     PendingIntentSender.getTaskIdForEntry(
134                         entryId,
135                         sameTaskSourceIds,
136                         requireActivity()
137                     ),
138                     entry,
139                     safetyCenterViewModel
140                 )
141 
142             if (sourceId == "AndroidPrivacyControls") {
143                 // No action required here because the privacy controls are rendered separately
144                 // by this fragment as generic preferences.
145             } else if (sourceId.endsWith("ActivityControls")) {
146                 subpageControlsExtraEntryGroup.addPreference(subpageEntry)
147             } else {
148                 subpageGenericEntryGroup.addPreference(subpageEntry)
149             }
150         }
151     }
152 
153     private fun renderPrivacyControls(prefStates: Map<Pref, PrefState>) {
154         fun setSwitchPreference(prefType: Pref) {
155             val switchPreference: ClickableDisabledSwitchPreference? = findPreference(prefType.key)
156             switchPreference?.setupState(
157                 prefStates[prefType],
158                 prefType,
159                 privacyControlsViewModel,
160                 this
161             )
162         }
163 
164         setSwitchPreference(Pref.MIC)
165         setSwitchPreference(Pref.CAMERA)
166         setSwitchPreference(Pref.CLIPBOARD)
167         setSwitchPreference(Pref.SHOW_PASSWORD)
168 
169         val locationEntry: Preference? = findPreference(Pref.LOCATION.key)
170         locationEntry?.setOnPreferenceClickListener {
171             privacyControlsViewModel.handlePrefClick(this, Pref.LOCATION, null)
172             true
173         }
174     }
175 
176     companion object {
177         private val TAG: String = PrivacySubpageFragment::class.java.simpleName
178         private const val BRAND_CHIP_KEY: String = "subpage_brand_chip"
179         private const val ISSUE_GROUP_KEY: String = "subpage_issue_group"
180         private const val GENERIC_ENTRY_GROUP_KEY: String = "subpage_generic_entry_group"
181         private const val CONTROLS_EXTRA_ENTRY_GROUP_KEY: String =
182             "subpage_controls_extra_entry_group"
183         /** Creates an instance of PrivacySubpageFragment with the arguments set */
184         @JvmStatic
185         fun newInstance(sessionId: Long): PrivacySubpageFragment {
186             val args = Bundle()
187             args.putLong(EXTRA_SESSION_ID, sessionId)
188 
189             val subpageFragment = PrivacySubpageFragment()
190             subpageFragment.setArguments(args)
191             return subpageFragment
192         }
193     }
194 }
195