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.text.format.DateFormat.getMediumDateFormat
23 import android.text.format.DateFormat.getTimeFormat
24 import android.util.Pair
25 import androidx.annotation.RequiresApi
26 import com.android.modules.utils.build.SdkLevel
27 import com.android.permissioncontroller.R
28 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage.GroupUsage
29 import com.android.permissioncontroller.permission.utils.StringUtils
30 import java.util.Locale
31 
32 const val SECONDS = 1
33 const val MINUTES = 2
34 const val HOURS = 3
35 const val DAYS = 4
36 
37 /**
38  * Whether to show the subattribution in the Permissions Dashboard
39  *
40  * @return whether to show subattribution in the Permissions Dashboard.
41  */
shouldShowSubattributionInPermissionsDashboardnull42 fun shouldShowSubattributionInPermissionsDashboard(): Boolean {
43     return SdkLevel.isAtLeastS()
44 }
45 
46 /**
47  * Build a string representing the given time if it happened on the current day and the date
48  * otherwise.
49  *
50  * @param context the context.
51  * @param lastAccessTime the time in milliseconds.
52  *
53  * @return a string representing the time or date of the given time or null if the time is 0.
54  */
getAbsoluteTimeStringnull55 fun getAbsoluteTimeString(context: Context, lastAccessTime: Long): String? {
56     if (lastAccessTime == 0L) {
57         return null
58     }
59     return if (isToday(lastAccessTime)) {
60         getTimeFormat(context).format(lastAccessTime)
61     } else {
62         getMediumDateFormat(context).format(lastAccessTime)
63     }
64 }
65 
66 /**
67  * Build a string representing the time of the most recent permission usage if it happened on
68  * the current day and the date otherwise.
69  *
70  * @param context the context.
71  * @param groupUsage the permission usage.
72  *
73  * @return a string representing the time or date of the most recent usage or null if there are
74  * no usages.
75  */
76 @RequiresApi(Build.VERSION_CODES.S)
getAbsoluteLastUsageStringnull77 fun getAbsoluteLastUsageString(context: Context, groupUsage: GroupUsage?): String? {
78     return if (groupUsage == null) {
79         null
80     } else getAbsoluteTimeString(context, groupUsage.lastAccessTime)
81 }
82 
83 /**
84  * Build a string representing the duration of a permission usage.
85  *
86  * @return a string representing the duration of this app's usage or null if there are no
87  * usages.
88  */
89 @RequiresApi(Build.VERSION_CODES.S)
getUsageDurationStringnull90 fun getUsageDurationString(context: Context, groupUsage: GroupUsage?): String? {
91     return if (groupUsage == null) {
92         null
93     } else getTimeDiffStr(context, groupUsage.accessDuration)
94 }
95 
96 /**
97  * Build a string representing the number of milliseconds passed in.  It rounds to the nearest
98  * unit.  For example, given a duration of 3500 and an English locale, this can return
99  * "3 seconds".
100  * @param context The context.
101  * @param duration The number of milliseconds.
102  * @return a string representing the given number of milliseconds.
103  */
getTimeDiffStrnull104 fun getTimeDiffStr(context: Context, duration: Long): String {
105     val timeDiffAndUnit = calculateTimeDiffAndUnit(duration)
106     return when (timeDiffAndUnit.second) {
107         SECONDS -> StringUtils.getIcuPluralsString(context,
108             R.string.seconds, timeDiffAndUnit.first.toInt())
109         MINUTES -> StringUtils.getIcuPluralsString(context,
110             R.string.minutes, timeDiffAndUnit.first.toInt())
111         HOURS -> StringUtils.getIcuPluralsString(context,
112             R.string.hours, timeDiffAndUnit.first.toInt())
113         else -> StringUtils.getIcuPluralsString(context,
114             R.string.days, timeDiffAndUnit.first.toInt())
115     }
116 }
117 
118 /**
119  * Build a string representing the duration used of milliseconds passed in.
120  * @return a string representing the duration used in the nearest unit. ex: Used for 3 mins
121  */
getDurationUsedStrnull122 fun getDurationUsedStr(context: Context, duration: Long): String {
123     val timeDiffAndUnit = calculateTimeDiffAndUnit(duration)
124     return when (timeDiffAndUnit.second) {
125         SECONDS -> StringUtils.getIcuPluralsString(context,
126             R.string.duration_used_seconds, timeDiffAndUnit.first.toInt())
127         MINUTES -> StringUtils.getIcuPluralsString(context,
128             R.string.duration_used_minutes, timeDiffAndUnit.first.toInt())
129         HOURS -> StringUtils.getIcuPluralsString(context,
130             R.string.duration_used_hours, timeDiffAndUnit.first.toInt())
131         else -> StringUtils.getIcuPluralsString(context,
132             R.string.duration_used_days, timeDiffAndUnit.first.toInt())
133     }
134 }
135 
136 /**
137  * Given the duration in milliseconds, calculate the time of that duration in the nearest unit.
138  * @return a Pair of the <duration in the nearest unit, the nearest unit>
139  */
calculateTimeDiffAndUnitnull140 fun calculateTimeDiffAndUnit(duration: Long): Pair<Long, Int> {
141     val seconds = Math.max(1, duration / 1000)
142 
143     if (seconds < 60) {
144         return Pair.create(seconds, SECONDS)
145     }
146     val minutes = seconds / 60
147     if (minutes < 60) {
148         return Pair.create(minutes, MINUTES)
149     }
150     val hours = minutes / 60
151     if (hours < 24) {
152         return Pair.create(hours, HOURS)
153     }
154     val days = hours / 24
155     return Pair.create(days, DAYS)
156 }
157 
158 /**
159  * Check whether the given time (in milliseconds) is in the current day.
160  *
161  * @param time the time in milliseconds
162  *
163  * @return whether the given time is in the current day.
164  */
isTodaynull165 private fun isToday(time: Long): Boolean {
166     val today: Calendar = Calendar.getInstance(Locale.getDefault())
167     today.setTimeInMillis(System.currentTimeMillis())
168     today.set(Calendar.HOUR_OF_DAY, 0)
169     today.set(Calendar.MINUTE, 0)
170     today.set(Calendar.SECOND, 0)
171     today.set(Calendar.MILLISECOND, 0)
172     val date: Calendar = Calendar.getInstance(Locale.getDefault())
173     date.setTimeInMillis(time)
174     return !date.before(today)
175 }
176