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  * @return a string representing the time or date of the given time or null if the time is 0.
53  */
getAbsoluteTimeStringnull54 fun getAbsoluteTimeString(context: Context, lastAccessTime: Long): String? {
55     if (lastAccessTime == 0L) {
56         return null
57     }
58     return if (isToday(lastAccessTime)) {
59         getTimeFormat(context).format(lastAccessTime)
60     } else {
61         getMediumDateFormat(context).format(lastAccessTime)
62     }
63 }
64 
65 /**
66  * Build a string representing the time of the most recent permission usage if it happened on the
67  * current day and the date otherwise.
68  *
69  * @param context the context.
70  * @param groupUsage the permission usage.
71  * @return a string representing the time or date of the most recent usage or null if there are no
72  *   usages.
73  */
74 @RequiresApi(Build.VERSION_CODES.S)
getAbsoluteLastUsageStringnull75 fun getAbsoluteLastUsageString(context: Context, groupUsage: GroupUsage?): String? {
76     return if (groupUsage == null) {
77         null
78     } else getAbsoluteTimeString(context, groupUsage.lastAccessTime)
79 }
80 
81 /**
82  * Build a string representing the duration of a permission usage.
83  *
84  * @return a string representing the duration of this app's usage or null if there are no usages.
85  */
86 @RequiresApi(Build.VERSION_CODES.S)
getUsageDurationStringnull87 fun getUsageDurationString(context: Context, groupUsage: GroupUsage?): String? {
88     return if (groupUsage == null) {
89         null
90     } else getTimeDiffStr(context, groupUsage.accessDuration)
91 }
92 
93 /**
94  * Build a string representing the number of milliseconds passed in. It rounds to the nearest unit.
95  * For example, given a duration of 3500 and an English locale, this can return "3 seconds".
96  *
97  * @param context The context.
98  * @param duration The number of milliseconds.
99  * @return a string representing the given number of milliseconds.
100  */
getTimeDiffStrnull101 fun getTimeDiffStr(context: Context, duration: Long): String {
102     val timeDiffAndUnit = calculateTimeDiffAndUnit(duration)
103     return when (timeDiffAndUnit.second) {
104         SECONDS ->
105             StringUtils.getIcuPluralsString(
106                 context,
107                 R.string.seconds,
108                 timeDiffAndUnit.first.toInt()
109             )
110         MINUTES ->
111             StringUtils.getIcuPluralsString(
112                 context,
113                 R.string.minutes,
114                 timeDiffAndUnit.first.toInt()
115             )
116         HOURS ->
117             StringUtils.getIcuPluralsString(context, R.string.hours, timeDiffAndUnit.first.toInt())
118         else ->
119             StringUtils.getIcuPluralsString(context, R.string.days, timeDiffAndUnit.first.toInt())
120     }
121 }
122 
123 /**
124  * Build a string representing the duration used of milliseconds passed in.
125  *
126  * @return a string representing the duration used in the nearest unit. ex: Used for 3 mins
127  */
getDurationUsedStrnull128 fun getDurationUsedStr(context: Context, duration: Long): String {
129     val timeDiffAndUnit = calculateTimeDiffAndUnit(duration)
130     return when (timeDiffAndUnit.second) {
131         SECONDS ->
132             StringUtils.getIcuPluralsString(
133                 context,
134                 R.string.duration_used_seconds,
135                 timeDiffAndUnit.first.toInt()
136             )
137         MINUTES ->
138             StringUtils.getIcuPluralsString(
139                 context,
140                 R.string.duration_used_minutes,
141                 timeDiffAndUnit.first.toInt()
142             )
143         HOURS ->
144             StringUtils.getIcuPluralsString(
145                 context,
146                 R.string.duration_used_hours,
147                 timeDiffAndUnit.first.toInt()
148             )
149         else ->
150             StringUtils.getIcuPluralsString(
151                 context,
152                 R.string.duration_used_days,
153                 timeDiffAndUnit.first.toInt()
154             )
155     }
156 }
157 
158 /**
159  * Given the duration in milliseconds, calculate the time of that duration in the nearest unit.
160  *
161  * @return a Pair of the <duration in the nearest unit, the nearest unit>
162  */
calculateTimeDiffAndUnitnull163 fun calculateTimeDiffAndUnit(duration: Long): Pair<Long, Int> {
164     val seconds = Math.max(1, duration / 1000)
165 
166     if (seconds < 60) {
167         return Pair.create(seconds, SECONDS)
168     }
169     val minutes = seconds / 60
170     if (minutes < 60) {
171         return Pair.create(minutes, MINUTES)
172     }
173     val hours = minutes / 60
174     if (hours < 24) {
175         return Pair.create(hours, HOURS)
176     }
177     val days = hours / 24
178     return Pair.create(days, DAYS)
179 }
180 
181 /**
182  * Check whether the given time (in milliseconds) is in the current day.
183  *
184  * @param time the time in milliseconds
185  * @return whether the given time is in the current day.
186  */
isTodaynull187 private fun isToday(time: Long): Boolean {
188     val today: Calendar = Calendar.getInstance(Locale.getDefault())
189     today.setTimeInMillis(System.currentTimeMillis())
190     today.set(Calendar.HOUR_OF_DAY, 0)
191     today.set(Calendar.MINUTE, 0)
192     today.set(Calendar.SECOND, 0)
193     today.set(Calendar.MILLISECOND, 0)
194     val date: Calendar = Calendar.getInstance(Locale.getDefault())
195     date.setTimeInMillis(time)
196     return !date.before(today)
197 }
198