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.d(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 private fun updateSafetyCenterEntries(entryGroup: SafetyCenterEntryGroup) { 121 Log.d(TAG, "updateSafetyCenterEntries called with $entryGroup") 122 subpageGenericEntryGroup.removeAll() 123 subpageControlsExtraEntryGroup.removeAll() 124 125 for (entry in entryGroup.entries) { 126 val entryId = entry.id 127 val sourceId = SafetyCenterIds.entryIdFromString(entryId).getSafetySourceId() 128 129 val subpageEntry = 130 SafetySubpageEntryPreference( 131 requireContext(), 132 PendingIntentSender.getTaskIdForEntry( 133 entryId, sameTaskSourceIds, requireActivity()), 134 entry, 135 safetyCenterViewModel) 136 137 if (sourceId == "AndroidPrivacyControls") { 138 // No action required here because the privacy controls are rendered separately 139 // by this fragment as generic preferences. 140 } else if (sourceId.endsWith("ActivityControls")) { 141 subpageControlsExtraEntryGroup.addPreference(subpageEntry) 142 } else { 143 subpageGenericEntryGroup.addPreference(subpageEntry) 144 } 145 } 146 } 147 148 private fun renderPrivacyControls(prefStates: Map<Pref, PrefState>) { 149 fun setSwitchPreference(prefType: Pref) { 150 val switchPreference: ClickableDisabledSwitchPreference? = findPreference(prefType.key) 151 switchPreference?.setupState( 152 prefStates[prefType], prefType, privacyControlsViewModel, this) 153 } 154 155 setSwitchPreference(Pref.MIC) 156 setSwitchPreference(Pref.CAMERA) 157 setSwitchPreference(Pref.CLIPBOARD) 158 setSwitchPreference(Pref.SHOW_PASSWORD) 159 160 val locationEntry: Preference? = findPreference(Pref.LOCATION.key) 161 locationEntry?.setOnPreferenceClickListener { 162 privacyControlsViewModel.handlePrefClick(this, Pref.LOCATION, null) 163 true 164 } 165 } 166 167 companion object { 168 private val TAG: String = PrivacySubpageFragment::class.java.simpleName 169 private const val BRAND_CHIP_KEY: String = "subpage_brand_chip" 170 private const val ISSUE_GROUP_KEY: String = "subpage_issue_group" 171 private const val GENERIC_ENTRY_GROUP_KEY: String = "subpage_generic_entry_group" 172 private const val CONTROLS_EXTRA_ENTRY_GROUP_KEY: String = 173 "subpage_controls_extra_entry_group" 174 /** Creates an instance of PrivacySubpageFragment with the arguments set */ 175 @JvmStatic 176 fun newInstance(sessionId: Long): PrivacySubpageFragment { 177 val args = Bundle() 178 args.putLong(EXTRA_SESSION_ID, sessionId) 179 180 val subpageFragment = PrivacySubpageFragment() 181 subpageFragment.setArguments(args) 182 return subpageFragment 183 } 184 } 185 } 186