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