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