1 /* 2 * Copyright (C) 2020 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.permission.ui.handheld.dashboard 18 19 import android.content.Context 20 import android.icu.util.Calendar 21 import android.provider.DeviceConfig 22 import android.text.format.DateFormat.getMediumDateFormat 23 import android.text.format.DateFormat.getTimeFormat 24 import android.util.Pair 25 import com.android.permissioncontroller.R 26 import com.android.permissioncontroller.permission.model.AppPermissionUsage.GroupUsage 27 import java.util.Locale 28 29 /** Whether to show the Permissions Hub. */ 30 private const val PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled" 31 32 /** Whether to show the mic and camera icons. */ 33 const val PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled" 34 35 /** Whether to show the location indicators. */ 36 const val PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled" 37 38 /* Whether location accuracy feature is enabled */ 39 const val PROPERTY_LOCATION_ACCURACY_ENABLED = "location_accuracy_enabled" 40 41 /* Default location precision */ 42 const val PROPERTY_LOCATION_PRECISION = "location_precision" 43 44 const val SECONDS = 1 45 const val MINUTES = 2 46 const val HOURS = 3 47 const val DAYS = 4 48 49 /** 50 * Whether the Permissions Hub 2 flag is enabled 51 * 52 * @return whether the flag is enabled 53 */ 54 fun isPermissionsHub2FlagEnabled(): Boolean { 55 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 56 PROPERTY_PERMISSIONS_HUB_2_ENABLED, false) 57 } 58 /** 59 * Whether to show the Permissions Dashboard 60 * 61 * @return whether to show the Permissions Dashboard. 62 */ 63 fun shouldShowPermissionsDashboard(): Boolean { 64 return isPermissionsHub2FlagEnabled() 65 } 66 67 /** 68 * Whether the Camera and Mic Icons are enabled by flag. 69 * 70 * @return whether the Camera and Mic Icons are enabled. 71 */ 72 fun isCameraMicIconsFlagEnabled(): Boolean { 73 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 74 PROPERTY_CAMERA_MIC_ICONS_ENABLED, true) 75 } 76 77 /** 78 * Whether to show Camera and Mic Icons. They should be shown if the permission hub, or the icons 79 * specifically, are enabled. 80 * 81 * @return whether to show the icons. 82 */ 83 fun shouldShowCameraMicIndicators(): Boolean { 84 return isCameraMicIconsFlagEnabled() || isPermissionsHub2FlagEnabled() 85 } 86 87 /** 88 * Whether the location indicators are enabled by flag. 89 * 90 * @return whether the location indicators are enabled by flag. 91 */ 92 fun isLocationIndicatorsFlagEnabled(): Boolean { 93 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 94 PROPERTY_LOCATION_INDICATORS_ENABLED, false) 95 } 96 97 /** 98 * Whether to show the location indicators. The location indicators are enable if the 99 * permission hub, or location indicator specifically are enabled. 100 */ 101 fun shouldShowLocationIndicators(): Boolean { 102 return isLocationIndicatorsFlagEnabled() || isPermissionsHub2FlagEnabled() 103 } 104 105 /** 106 * Whether the location accuracy feature is enabled 107 */ 108 fun isLocationAccuracyEnabled(): Boolean { 109 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 110 PROPERTY_LOCATION_ACCURACY_ENABLED, true) 111 } 112 113 /** 114 * Default state of location precision 115 * true: default is FINE. 116 * false: default is COARSE. 117 */ 118 fun getDefaultPrecision(): Boolean { 119 return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 120 PROPERTY_LOCATION_PRECISION, true) 121 } 122 123 /** 124 * Build a string representing the given time if it happened on the current day and the date 125 * otherwise. 126 * 127 * @param context the context. 128 * @param lastAccessTime the time in milliseconds. 129 * 130 * @return a string representing the time or date of the given time or null if the time is 0. 131 */ 132 fun getAbsoluteTimeString(context: Context, lastAccessTime: Long): String? { 133 if (lastAccessTime == 0L) { 134 return null 135 } 136 return if (isToday(lastAccessTime)) { 137 getTimeFormat(context).format(lastAccessTime) 138 } else { 139 getMediumDateFormat(context).format(lastAccessTime) 140 } 141 } 142 143 /** 144 * Build a string representing the time of the most recent permission usage if it happened on 145 * the current day and the date otherwise. 146 * 147 * @param context the context. 148 * @param groupUsage the permission usage. 149 * 150 * @return a string representing the time or date of the most recent usage or null if there are 151 * no usages. 152 */ 153 fun getAbsoluteLastUsageString(context: Context, groupUsage: GroupUsage?): String? { 154 return if (groupUsage == null) { 155 null 156 } else getAbsoluteTimeString(context, groupUsage.lastAccessTime) 157 } 158 159 /** 160 * Build a string representing the duration of a permission usage. 161 * 162 * @return a string representing the duration of this app's usage or null if there are no 163 * usages. 164 */ 165 fun getUsageDurationString(context: Context, groupUsage: GroupUsage?): String? { 166 return if (groupUsage == null) { 167 null 168 } else getTimeDiffStr(context, groupUsage.accessDuration) 169 } 170 171 /** 172 * Build a string representing the number of milliseconds passed in. It rounds to the nearest 173 * unit. For example, given a duration of 3500 and an English locale, this can return 174 * "3 seconds". 175 * @param context The context. 176 * @param duration The number of milliseconds. 177 * @return a string representing the given number of milliseconds. 178 */ 179 fun getTimeDiffStr(context: Context, duration: Long): String { 180 val timeDiffAndUnit = calculateTimeDiffAndUnit(duration) 181 return when (timeDiffAndUnit.second) { 182 SECONDS -> context.resources.getQuantityString(R.plurals.seconds, 183 timeDiffAndUnit.first.toInt(), timeDiffAndUnit.first) 184 MINUTES -> context.resources.getQuantityString(R.plurals.minutes, 185 timeDiffAndUnit.first.toInt(), timeDiffAndUnit.first) 186 HOURS -> context.resources.getQuantityString(R.plurals.hours, 187 timeDiffAndUnit.first.toInt(), timeDiffAndUnit.first) 188 else -> context.resources.getQuantityString(R.plurals.days, 189 timeDiffAndUnit.first.toInt(), timeDiffAndUnit.first) 190 } 191 } 192 193 /** 194 * Build a string representing the duration used of milliseconds passed in. 195 * @return a string representing the duration used in the nearest unit. ex: Used for 3 mins 196 */ 197 fun getDurationUsedStr(context: Context, duration: Long): String { 198 val timeDiffAndUnit = calculateTimeDiffAndUnit(duration) 199 return when (timeDiffAndUnit.second) { 200 SECONDS -> context.resources.getQuantityString(R.plurals.duration_used_seconds, 201 timeDiffAndUnit.first.toInt(), timeDiffAndUnit.first) 202 MINUTES -> context.resources.getQuantityString(R.plurals.duration_used_minutes, 203 timeDiffAndUnit.first.toInt(), timeDiffAndUnit.first) 204 HOURS -> context.resources.getQuantityString(R.plurals.duration_used_hours, 205 timeDiffAndUnit.first.toInt(), timeDiffAndUnit.first) 206 else -> context.resources.getQuantityString(R.plurals.duration_used_days, 207 timeDiffAndUnit.first.toInt(), timeDiffAndUnit.first) 208 } 209 } 210 211 /** 212 * Given the duration in milliseconds, calculate the time of that duration in the nearest unit. 213 * @return a Pair of the <duration in the nearest unit, the nearest unit> 214 */ 215 fun calculateTimeDiffAndUnit(duration: Long): Pair<Long, Int> { 216 val seconds = Math.max(1, duration / 1000) 217 218 if (seconds < 60) { 219 return Pair.create(seconds, SECONDS) 220 } 221 val minutes = seconds / 60 222 if (minutes < 60) { 223 return Pair.create(minutes, MINUTES) 224 } 225 val hours = minutes / 60 226 if (hours < 24) { 227 return Pair.create(hours, HOURS) 228 } 229 val days = hours / 24 230 return Pair.create(days, DAYS) 231 } 232 233 /** 234 * Check whether the given time (in milliseconds) is in the current day. 235 * 236 * @param time the time in milliseconds 237 * 238 * @return whether the given time is in the current day. 239 */ 240 private fun isToday(time: Long): Boolean { 241 val today: Calendar = Calendar.getInstance(Locale.getDefault()) 242 today.setTimeInMillis(System.currentTimeMillis()) 243 today.set(Calendar.HOUR_OF_DAY, 0) 244 today.set(Calendar.MINUTE, 0) 245 today.set(Calendar.SECOND, 0) 246 today.set(Calendar.MILLISECOND, 0) 247 val date: Calendar = Calendar.getInstance(Locale.getDefault()) 248 date.setTimeInMillis(time) 249 return !date.before(today) 250 } 251