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