1 /*
2  * 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 @file:Suppress("DEPRECATION")
17 
18 package com.android.permissioncontroller.permission.ui.auto.dashboard
19 
20 import android.app.role.RoleManager
21 import android.content.Intent
22 import android.os.Build
23 import android.os.Bundle
24 import android.text.format.DateFormat
25 import androidx.annotation.RequiresApi
26 import androidx.lifecycle.ViewModelProvider
27 import androidx.preference.Preference
28 import androidx.preference.PreferenceCategory
29 import androidx.preference.PreferenceScreen
30 import com.android.car.ui.preference.CarUiPreference
31 import com.android.permissioncontroller.Constants
32 import com.android.permissioncontroller.DumpableLog
33 import com.android.permissioncontroller.PermissionControllerApplication
34 import com.android.permissioncontroller.PermissionControllerStatsLog
35 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION
36 import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED
37 import com.android.permissioncontroller.R
38 import com.android.permissioncontroller.auto.AutoSettingsFrameFragment
39 import com.android.permissioncontroller.permission.model.legacy.PermissionApps.AppDataLoader
40 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
41 import com.android.permissioncontroller.permission.model.v31.PermissionUsages
42 import com.android.permissioncontroller.permission.model.v31.PermissionUsages.PermissionsUsagesChangeCallback
43 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
44 import com.android.permissioncontroller.permission.ui.auto.AutoDividerPreference
45 import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelFactoryLegacy
46 import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelLegacy
47 import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
48 import com.android.permissioncontroller.permission.utils.Utils
49 import java.time.Clock
50 import java.time.Instant
51 import java.time.ZoneId
52 import java.time.ZonedDateTime
53 import java.time.temporal.ChronoUnit
54 import java.util.concurrent.atomic.AtomicReference
55 
56 @RequiresApi(Build.VERSION_CODES.S)
57 class AutoPermissionUsageDetailsFragment :
58     AutoSettingsFrameFragment(), PermissionsUsagesChangeCallback {
59 
60     companion object {
61         private const val LOG_TAG = "AutoPermissionUsageDetailsFragment"
62         private const val KEY_SESSION_ID = "_session_id"
63         private const val FILTER_24_HOURS = 2
64         private val MIDNIGHT_TODAY =
65             ZonedDateTime.now(ZoneId.systemDefault()).truncatedTo(ChronoUnit.DAYS).toEpochSecond() *
66                 1000L
67         private val MIDNIGHT_YESTERDAY =
68             ZonedDateTime.now(ZoneId.systemDefault())
69                 .minusDays(1)
70                 .truncatedTo(ChronoUnit.DAYS)
71                 .toEpochSecond() * 1000L
72 
73         // Only show the last 24 hours on Auto right now
74         private const val SHOW_7_DAYS = false
75 
76         /** Creates a new instance of [AutoPermissionUsageDetailsFragment]. */
newInstancenull77         fun newInstance(
78             groupName: String?,
79             showSystem: Boolean,
80             sessionId: Long
81         ): AutoPermissionUsageDetailsFragment {
82             return AutoPermissionUsageDetailsFragment().apply {
83                 arguments =
84                     Bundle().apply {
85                         putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
86                         putLong(Constants.EXTRA_SESSION_ID, sessionId)
87                         putBoolean(ManagePermissionsActivity.EXTRA_SHOW_SYSTEM, showSystem)
88                     }
89             }
90         }
91     }
92 
93     private val SESSION_ID_KEY = (AutoPermissionUsageFragment::class.java.name + KEY_SESSION_ID)
94 
95     private lateinit var permissionUsages: PermissionUsages
96     private lateinit var usageViewModel: PermissionUsageDetailsViewModelLegacy
97     private lateinit var filterGroup: String
98     private lateinit var roleManager: RoleManager
99 
100     private var appPermissionUsages: List<AppPermissionUsage> = listOf()
101     private var showSystem = false
102     private var finishedInitialLoad = false
103     private var hasSystemApps = false
104 
105     /** Unique Id of a request */
106     private var sessionId: Long = 0
107 
onCreatenull108     override fun onCreate(savedInstanceState: Bundle?) {
109         super.onCreate(savedInstanceState)
110         if (arguments == null) {
111             DumpableLog.e(LOG_TAG, "Missing arguments")
112             activity?.finish()
113             return
114         }
115         if (
116             !requireArguments().containsKey(Intent.EXTRA_PERMISSION_GROUP_NAME) or
117                 (requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME) == null)
118         ) {
119             DumpableLog.e(LOG_TAG, "Missing argument ${Intent.EXTRA_USER}")
120             activity?.finish()
121             return
122         }
123         filterGroup = requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME)!!
124         showSystem =
125             requireArguments().getBoolean(ManagePermissionsActivity.EXTRA_SHOW_SYSTEM, false)
126         sessionId =
127             savedInstanceState?.getLong(SESSION_ID_KEY)
128                 ?: (arguments?.getLong(Constants.EXTRA_SESSION_ID, Constants.INVALID_SESSION_ID)
129                     ?: Constants.INVALID_SESSION_ID)
130         headerLabel =
131             resources.getString(
132                 R.string.permission_group_usage_title,
133                 getPermGroupLabel(requireContext(), filterGroup)
134             )
135 
136         val context = preferenceManager.getContext()
137         permissionUsages = PermissionUsages(context)
138         roleManager = Utils.getSystemServiceSafe(context, RoleManager::class.java)
139         val usageViewModelFactory =
140             PermissionUsageDetailsViewModelFactoryLegacy(
141                 PermissionControllerApplication.get(),
142                 roleManager,
143                 filterGroup,
144                 sessionId
145             )
146         usageViewModel =
147             ViewModelProvider(this, usageViewModelFactory)[
148                 PermissionUsageDetailsViewModelLegacy::class.java]
149 
150         reloadData()
151     }
152 
onCreatePreferencesnull153     override fun onCreatePreferences(bundlle: Bundle?, s: String?) {
154         preferenceScreen = preferenceManager.createPreferenceScreen(context!!)
155     }
156 
setupHeaderPreferencesnull157     private fun setupHeaderPreferences() {
158         addTimelineDescriptionPreference()
159         preferenceScreen.addPreference(AutoDividerPreference(context))
160         addManagePermissionPreference()
161         preferenceScreen.addPreference(AutoDividerPreference(context))
162     }
163 
164     /** Reloads the data to show. */
reloadDatanull165     private fun reloadData() {
166         usageViewModel.loadPermissionUsages(
167             requireActivity().getLoaderManager(),
168             permissionUsages,
169             this,
170             FILTER_24_HOURS
171         )
172         if (finishedInitialLoad) {
173             setLoading(true)
174         }
175     }
176 
onPermissionUsagesChangednull177     override fun onPermissionUsagesChanged() {
178         if (permissionUsages.usages.isEmpty()) {
179             return
180         }
181         appPermissionUsages = ArrayList(permissionUsages.usages)
182         updateUI()
183     }
184 
updateSystemTogglenull185     private fun updateSystemToggle() {
186         if (!showSystem) {
187             PermissionControllerStatsLog.write(
188                 PERMISSION_USAGE_FRAGMENT_INTERACTION,
189                 sessionId,
190                 PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED
191             )
192         }
193         showSystem = !showSystem
194         updateAction()
195         updateUI()
196     }
197 
updateActionnull198     private fun updateAction() {
199         if (!hasSystemApps) {
200             setAction(null, null)
201             return
202         }
203         val label =
204             if (showSystem) {
205                 getString(R.string.menu_hide_system)
206             } else {
207                 getString(R.string.menu_show_system)
208             }
209         setAction(label) { updateSystemToggle() }
210     }
211 
updateUInull212     private fun updateUI() {
213         if (appPermissionUsages.isEmpty()) {
214             return
215         }
216         preferenceScreen.removeAll()
217         setupHeaderPreferences()
218 
219         val uiData =
220             usageViewModel.buildPermissionUsageDetailsUiData(
221                 appPermissionUsages,
222                 showSystem,
223                 SHOW_7_DAYS
224             )
225 
226         if (hasSystemApps != uiData.shouldDisplayShowSystemToggle) {
227             hasSystemApps = uiData.shouldDisplayShowSystemToggle
228             updateAction()
229         }
230 
231         val category = AtomicReference(PreferenceCategory(requireContext()))
232         preferenceScreen.addPreference(category.get())
233 
234         AppDataLoader(context) {
235                 renderHistoryPreferences(
236                     uiData.getHistoryPreferenceDataList(),
237                     category,
238                     preferenceScreen
239                 )
240 
241                 setLoading(false)
242                 finishedInitialLoad = true
243                 permissionUsages.stopLoader(requireActivity().getLoaderManager())
244             }
245             .execute(*uiData.permissionApps.toTypedArray())
246     }
247 
createPermissionHistoryPreferencenull248     fun createPermissionHistoryPreference(
249         historyPreferenceData: PermissionUsageDetailsViewModelLegacy.HistoryPreferenceData
250     ): Preference {
251         return AutoPermissionHistoryPreference(requireContext(), historyPreferenceData)
252     }
253 
addTimelineDescriptionPreferencenull254     private fun addTimelineDescriptionPreference() {
255         val preference =
256             CarUiPreference(context).apply {
257                 summary =
258                     getString(
259                         R.string.permission_group_usage_subtitle_24h,
260                         getPermGroupLabel(requireContext(), filterGroup)
261                     )
262                 isSelectable = false
263             }
264         preferenceScreen.addPreference(preference)
265     }
266 
addManagePermissionPreferencenull267     private fun addManagePermissionPreference() {
268         val preference =
269             CarUiPreference(context).apply {
270                 title = getString(R.string.manage_permission)
271                 summary =
272                     getString(
273                         R.string.manage_permission_summary,
274                         getPermGroupLabel(requireContext(), filterGroup)
275                     )
276                 onPreferenceClickListener =
277                     Preference.OnPreferenceClickListener {
278                         val intent =
279                             Intent(Intent.ACTION_MANAGE_PERMISSION_APPS).apply {
280                                 putExtra(Intent.EXTRA_PERMISSION_NAME, filterGroup)
281                             }
282                         startActivity(intent)
283                         true
284                     }
285             }
286         preferenceScreen.addPreference(preference)
287     }
288 
289     /** Render the provided [historyPreferenceDataList] into the [preferenceScreen] UI. */
renderHistoryPreferencesnull290     fun renderHistoryPreferences(
291         historyPreferenceDataList:
292             List<PermissionUsageDetailsViewModelLegacy.HistoryPreferenceData>,
293         category: AtomicReference<PreferenceCategory>,
294         preferenceScreen: PreferenceScreen,
295     ) {
296         var previousDateMs = 0L
297         historyPreferenceDataList.forEach {
298             val usageTimestamp = it.accessEndTime
299             val currentDateMs =
300                 ZonedDateTime.ofInstant(
301                         Instant.ofEpochMilli(usageTimestamp),
302                         Clock.system(ZoneId.systemDefault()).zone
303                     )
304                     .truncatedTo(ChronoUnit.DAYS)
305                     .toEpochSecond() * 1000L
306             if (currentDateMs != previousDateMs) {
307                 if (previousDateMs != 0L) {
308                     category.set(PreferenceCategory(requireContext()))
309                     preferenceScreen.addPreference(category.get())
310                 }
311                 if (usageTimestamp > MIDNIGHT_TODAY) {
312                     category.get().setTitle(R.string.permission_history_category_today)
313                 } else if (usageTimestamp > MIDNIGHT_YESTERDAY) {
314                     category.get().setTitle(R.string.permission_history_category_yesterday)
315                 } else {
316                     category.get().setTitle(DateFormat.getDateFormat(context).format(currentDateMs))
317                 }
318                 previousDateMs = currentDateMs
319             }
320             category.get().addPreference(createPermissionHistoryPreference(it))
321         }
322     }
323 }
324