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