• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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