1 /* <lambda>null2 * 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.TIRAMISU 20 import android.os.Bundle 21 import android.safetycenter.SafetyCenterErrorDetails 22 import android.widget.Toast 23 import androidx.annotation.RequiresApi 24 import androidx.lifecycle.ViewModelProvider 25 import androidx.preference.PreferenceFragmentCompat 26 import androidx.preference.PreferenceScreen 27 import androidx.recyclerview.widget.RecyclerView 28 import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID 29 import com.android.permissioncontroller.Constants.INVALID_SESSION_ID 30 import com.android.permissioncontroller.safetycenter.SafetyCenterConstants.QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT 31 import com.android.permissioncontroller.safetycenter.ui.ParsedSafetyCenterIntent.Companion.toSafetyCenterIntent 32 import com.android.permissioncontroller.safetycenter.ui.model.LiveSafetyCenterViewModelFactory 33 import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData 34 import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel 35 import com.android.safetycenter.resources.SafetyCenterResourcesApk 36 37 /** A base fragment that represents a page in Safety Center. */ 38 @RequiresApi(TIRAMISU) 39 abstract class SafetyCenterFragment : PreferenceFragmentCompat() { 40 41 lateinit var safetyCenterViewModel: SafetyCenterViewModel 42 lateinit var sameTaskSourceIds: List<String> 43 lateinit var collapsableIssuesCardHelper: CollapsableIssuesCardHelper 44 var safetyCenterSessionId = INVALID_SESSION_ID 45 private val highlightManager = PreferenceHighlightManager(this) 46 47 override fun onCreate(savedInstanceState: Bundle?) { 48 super.onCreate(savedInstanceState) 49 highlightManager.restoreState(savedInstanceState) 50 } 51 52 override fun onCreateAdapter( 53 preferenceScreen: PreferenceScreen 54 ): RecyclerView.Adapter<RecyclerView.ViewHolder> { 55 /* The scroll-to-result functionality for settings search is currently implemented only for 56 * subpages i.e. non expand-and-collapse type entries. Hence, we check that the flag is 57 * enabled before using an adapter that does the highlighting and scrolling. */ 58 val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = 59 if (SafetyCenterUiFlags.getShowSubpages()) { 60 highlightManager.createAdapter(preferenceScreen) 61 } else { 62 super.onCreateAdapter(preferenceScreen) 63 } 64 65 /* By default, the PreferenceGroupAdapter does setHasStableIds(true). Since each Preference 66 * is internally allocated with an auto-incremented ID, it does not allow us to gracefully 67 * update only changed preferences based on SafetyPreferenceComparisonCallback. In order to 68 * allow the list to track the changes, we need to ignore the Preference IDs. */ 69 adapter.setHasStableIds(false) 70 return adapter 71 } 72 73 override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 74 sameTaskSourceIds = 75 SafetyCenterResourcesApk(requireContext()) 76 .getStringByName("config_same_task_safety_source_ids") 77 .split(",") 78 safetyCenterSessionId = requireArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID) 79 80 safetyCenterViewModel = 81 ViewModelProvider( 82 requireActivity(), 83 LiveSafetyCenterViewModelFactory(requireActivity().getApplication()) 84 ) 85 .get(SafetyCenterViewModel::class.java) 86 safetyCenterViewModel.safetyCenterUiLiveData.observe(this) { uiData: SafetyCenterUiData? -> 87 renderSafetyCenterData(uiData) 88 } 89 safetyCenterViewModel.errorLiveData.observe(this) { errorDetails: SafetyCenterErrorDetails? 90 -> 91 displayErrorDetails(errorDetails) 92 } 93 94 val safetyCenterIntent: ParsedSafetyCenterIntent = 95 requireActivity().intent.toSafetyCenterIntent() 96 val isQsFragment = 97 getArguments()?.getBoolean(QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT, false) ?: false 98 collapsableIssuesCardHelper = 99 CollapsableIssuesCardHelper(safetyCenterViewModel, sameTaskSourceIds) 100 collapsableIssuesCardHelper.apply { 101 setFocusedIssueKey(safetyCenterIntent.safetyCenterIssueKey) 102 // Set quick settings state first and allow restored state to override if necessary 103 setQuickSettingsState(isQsFragment, safetyCenterIntent.shouldExpandIssuesGroup) 104 restoreState(savedInstanceState) 105 } 106 107 getPreferenceManager().setPreferenceComparisonCallback(SafetyPreferenceComparisonCallback()) 108 } 109 110 override fun onBindPreferences() { 111 super.onBindPreferences() 112 highlightManager.registerObserverIfNeeded() 113 } 114 115 override fun onUnbindPreferences() { 116 super.onUnbindPreferences() 117 highlightManager.unregisterObserverIfNeeded() 118 } 119 120 override fun onStart() { 121 super.onStart() 122 configureInteractionLogger() 123 logSafetyCenterViewedEvent() 124 } 125 126 override fun onResume() { 127 super.onResume() 128 highlightManager.highlightPreferenceIfNeeded() 129 } 130 131 override fun onSaveInstanceState(outState: Bundle) { 132 super.onSaveInstanceState(outState) 133 collapsableIssuesCardHelper.saveState(outState) 134 highlightManager.saveState(outState) 135 } 136 137 override fun onStop() { 138 super.onStop() 139 safetyCenterViewModel.interactionLogger.clearViewedIssues() 140 } 141 142 override fun onDestroy() { 143 super.onDestroy() 144 if (activity?.isChangingConfigurations == true) { 145 safetyCenterViewModel.changingConfigurations() 146 } 147 } 148 149 /** 150 * Insert preferences for whatever Safety Center data we currently have available. 151 * 152 * This should contain the groups and entries to render the basic page structure, even if no 153 * source has responded with data at this point. 154 * 155 * This should be called by subclasses in [onCreatePreferences] after they've pulled out the 156 * preferences they will modify in [renderSafetyCenterData]. 157 */ 158 protected fun prerenderCurrentSafetyCenterData() = 159 renderSafetyCenterData(safetyCenterViewModel.getCurrentSafetyCenterDataAsUiData()) 160 161 abstract fun renderSafetyCenterData(uiData: SafetyCenterUiData?) 162 163 abstract fun configureInteractionLogger() 164 165 private fun logSafetyCenterViewedEvent() { 166 // If Safety Center was opened due to an associated notification click (i.e. intent has an 167 // associated issue), record that issue's metadata on the SAFETY_CENTER_VIEWED event 168 val maybeIssueKey = requireActivity().intent.toSafetyCenterIntent().safetyCenterIssueKey 169 val maybeIssue = 170 maybeIssueKey?.let { 171 safetyCenterViewModel.getCurrentSafetyCenterDataAsUiData().getMatchingIssue(it) 172 } 173 174 if (maybeIssue == null) { 175 safetyCenterViewModel.interactionLogger.record(Action.SAFETY_CENTER_VIEWED) 176 } else { 177 safetyCenterViewModel.interactionLogger.recordForIssue( 178 Action.SAFETY_CENTER_VIEWED, 179 maybeIssue, 180 isDismissed = false 181 ) 182 } 183 } 184 185 private fun displayErrorDetails(errorDetails: SafetyCenterErrorDetails?) { 186 if (errorDetails == null) return 187 Toast.makeText(requireContext(), errorDetails.errorMessage, Toast.LENGTH_LONG).show() 188 safetyCenterViewModel.clearError() 189 } 190 } 191