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